- Converter の使用例
- Converter の自動適用
- Embedded 属性の変換
- Collection 属性の変換
- クラスレベルでの Converter 指定
- orm.xml での Converter 指定
- 注意点
データベースのカラムと Entity の該当する属性の変換を Converter で定義できるようになりました。
Converter の使用例
Converter は以下の AttributeConverterインターフェース を実装して定義します。
public interface AttributeConverter<X,Y> { public Y convertToDatabaseColumn (X attribute); public X convertToEntityAttribute (Y dbData); }
Entity の属性は Boolean 型で、データベース上では数値で扱う場合には以下のような Converter を定義します。
@Converter public class BooleanToIntegerConverter implements AttributeConverter<Boolean, Integer> { @Override public Integer convertToDatabaseColumn (Boolean attribute) { return attribute ? 1 : 0; } @Override public Boolean convertToEntityAttribute (Integer dbData) { return dbData > 0; } }
そして Entity の当該属性に @Convert
アノテーションで利用する Converter を指定します。
@Convert(converter = BooleanToIntegerConverter.class) private Boolean bonded;
これによりデータベースへの永続化時とデータベースからの読み込み時に自動的に型変換が適用されるようになります。
JSF の javax.faces.convert.Converter
と同じような感じですね。
ID 属性と Version 属性、関連の属性、Enumerated や Temporal アノテーションが付けられている属性は変換の対象にはなりません。
また、複数のデータベースカラムを扱うことは現時点で標準化されていません。
Converter の自動適用
Converter の定義で autoApply
を設定すると、Entity の属性値に @Convert
を指定しなくとも暗黙的に対象の型に対して変換が適用できます。
URL 型に対して暗黙的に型変換を行うには以下のように定義します。
@Converter(autoApply=true) public class URLConverter implements AttributeConverter<URL, String> { public String convertToDatabaseColumn (URL attribute) { return attribute.toString(); } public URL convertToEntityAttribute (String dbData) { return new URL(dbData); } }
@Converter
に autoApply=true
を定義してあげるだけです(URLの変換時の例外コードは省略しています)。
型変換の対象から外したい場合には disableConversion
で無効化します。
@Convert(desableConversion=true)
URL homePage;
暗黙的な自動変換が定義されていたとしても、属性に対して @Convert
アノテートすることで、自動変換の定義を上書きすることができます。
Embedded 属性の変換
Embedded として属性を定義している場合には、attributeName
で対象の属性を指定します。
以下のような @Embeddable
なクラスが bonded という属性を持っていた場合、
@Embeddable public class SecurityInfo { private Boolean bonded; // ・・・ }
Entity 側の @Convert
で属性値を指定します。
@Entity public class Employee { @Embedded @Convert(converter = BooleanToIntegerConverter.class, attributeName = "bonded") private SecurityInfo securityInfo; }
より多くの変換が必要な場合は以下のように @Converts
の中に複数定義することができます。
@Embedded @Converts({ @Convert(attributeName = "level", converter = LevelConverter.class), @Convert(attributeName = "health", converter = HealthConverter.class), @Convert(attributeName = "status.runningStatus", converter = RunningStatusConverter.class) }) protected RunnerInfo info;
この例にあるように "status.runningStatus" のように属性名をドットで連結することでネストした属性に対して変換処理を定義できます。
Collection 属性の変換
ElementCollection
の単純な変換は以下のように特別なものは不要です。
@ElementCollection @Convert(converter = BooleanToIntegerConverter.class) public List<Boolean> securityClearances;
単純な Map の場合、値に対する変換は以下のように定義します。
@ElementCollection @Convert(converter = EmployeeNameConverter.class) Map<String, String> responsibilities;
キーに対する変換が必要な場合には、"key" を指定します。
@ElementCollection @Converts({ @Convert(attributeName = "key", converter = DistanceConverter.class), @Convert(converter = EmployeeNameConverter.class) }) protected Map<String, String> responsibilities;
通常の OneToMany 関連のキーに適用する場合にも "key" で指定します。
@OneToMany @Convert(converter=ResponsibilityCodeConverter.class, attributeName="key") Map<String, Employee> responsibilities;
ネストして適用する場合には "key." または "value." で連結させて指定することができます。
@Entity public class PropertyRecord { @ElementCollection @Convert(converter=CityConverter.class, attributeName="key.region.city") Map<Address, PropertyInfo> parcels; }
関連の Embeddable に対しても同じような指定で対応できます。
@ManyToMany @Convert(converter = UpperCaseConverter.class, attributeName="key.lastName") private Map<EmployeeName, Employee> employees;
クラスレベルでの Converter 指定
Converter は継承した子クラスから指定することもできます。
以下の親クラスがあった場合に、
@MappedSuperclass public class Athlete { protected Integer age; @ElementCollection protected Map<String, Date> accomplishments; // ... }
以下のように継承した属性に対して Convert を定義することもできます。
@Entity @Converts({ @Convert(attributeName = "accomplishments.key", converter = AccomplishmentConverter.class), @Convert(attributeName = "accomplishments", converter = DateConverter.class), @Convert(attributeName = "age", converter = AgeConverter.class) }) public class Runner extends Athlete { ... }
orm.xml での Converter 指定
CreditCard 番号をデータベースには暗号化した文字列として保存したいとします。
CreditCard Entity は以下のようになり、
@Entity public class CreditCard { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String ccNumber; private String name; ... }
暗号化用のコンバータは以下のようになります。
@Converter public class CryptoConverter implements AttributeConverter<String, String> { private static final String ALGORITHM = "AES/ECB/PKCS5Padding"; private static final byte[] KEY = "MySuperSecretKey".getBytes(); @Override public String convertToDatabaseColumn(String ccNumber) { // do some encryption Key key = new SecretKeySpec(KEY, "AES"); try { Cipher c = Cipher.getInstance(ALGORITHM); c.init(Cipher.ENCRYPT_MODE, key); return Base64.encodeBytes(c.doFinal(ccNumber.getBytes())); } catch (Exception e) { throw new RuntimeException(e); } } @Override public String convertToEntityAttribute(String dbData) { // do some decryption Key key = new SecretKeySpec(KEY, "AES"); try { Cipher c = Cipher.getInstance(ALGORITHM); c.init(Cipher.DECRYPT_MODE, key); return new String(c.doFinal(Base64.decode(dbData))); } catch (Exception e) { throw new RuntimeException(e); } } }
orm.xml
にて CreditCard の ccNumber にコンバータを適用するには以下のように定義します。
<entity-mappings version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm_2_1.xsd"> <entity class="sample.entity.CreditCard"> <convert converter="sample.converter.CryptoConverter" attribute-name="ccNumber"/> </entity> </entity-mappings>
convert タグで対象のコンバータクラスを属性名を指定しています。
注意点
一番最初の例の BooleanToIntegerConverter
を定義していた場合、以下の JPQL は上手く変換されます。
SELECT e FROM e WHERE e.bonded = true
true というリテラルはコンバータを介して Integer に変換されるので有効な SQL になります。
ただし、以下の JPQL を書いた場合、
SELECT e FROM e WHERE NOT e.bonded
bonded はデータベース上は Integer なので SQLエラーになる可能性があります。 このように値に対する変換は行われても、演算子に対する変換は行われません。
また UPPER() などの関数を含む場合や LIKE のような演算のリテラル値に対しても変換は適用されない可能性があるため実際に出力されたSQLを良く確認する必要があるでしょう。
詳細は以下の書籍をご覧ください。
Pro JPA 2 (Expert's Voice in Java)
- 作者: Mike Keith,Merrick Schincariol
- 出版社/メーカー: Apress
- 発売日: 2013/09/26
- メディア: ペーパーバック
- この商品を含むブログを見る