Hibernateでは、エンティティのプロパティとデータベースカラム間の型変換をカスタマイズするためのAttributeConverterインターフェースが提供されています。これにより、標準のマッピングでは対応できない複雑な型変換を実現できます。
基本プロパティの変換実装
まず、金額と通貨を扱うカスタムクラスを作成します:
package com.example.hibernate.model;
import java.math.BigDecimal;
import java.util.Currency;
public class CurrencyAmount {
private static final String DELIMITER = "|";
private final BigDecimal amount;
private final Currency currency;
public CurrencyAmount(BigDecimal amount, Currency currency) {
this.amount = amount;
this.currency = currency;
}
public BigDecimal getAmount() {
return amount;
}
public Currency getCurrency() {
return currency;
}
@Override
public String toString() {
return amount.toString() + DELIMITER + currency.getCurrencyCode();
}
public static CurrencyAmount parse(String input) {
String[] parts = input.split(DELIMITER);
return new CurrencyAmount(
new BigDecimal(parts[0]),
Currency.getInstance(parts[1])
);
}
}
次に、AttributeConverterを実装したコンバータクラスを作成します:
package com.example.hibernate.converter;
import javax.persistence.AttributeConverter;
import com.example.hibernate.model.CurrencyAmount;
public class CurrencyAmountConverter implements AttributeConverter<CurrencyAmount, String> {
@Override
public String convertToDatabaseColumn(CurrencyAmount attribute) {
return attribute != null ? attribute.toString() : null;
}
@Override
public CurrencyAmount convertToEntityAttribute(String dbData) {
return dbData != null ? CurrencyAmount.parse(dbData) : null;
}
}
エンティティクラスでコンバータを適用します:
package com.example.hibernate.entity;
import javax.persistence.*;
import com.example.hibernate.model.CurrencyAmount;
import com.example.hibernate.converter.CurrencyAmountConverter;
@Entity
@Table(name = "accounts")
public class Account {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long accountId;
@Convert(converter = CurrencyAmountConverter.class)
private CurrencyAmount balance;
public Long getAccountId() {
return accountId;
}
public void setAccountId(Long accountId) {
this.accountId = accountId;
}
public CurrencyAmount getBalance() {
return balance;
}
public void setBalance(CurrencyAmount balance) {
this.balance = balance;
}
}
テストコードの実装例:
@Test
public void persistAccountWithCustomCurrency() {
Account account = new Account();
account.setBalance(new CurrencyAmount(
new BigDecimal("5000.50"),
Currency.getInstance("JPY")
));
entityManager.persist(account);
Account retrieved = entityManager.find(Account.class, account.getAccountId());
assertNotNull(retrieved.getBalance());
assertEquals("JPY", retrieved.getBalance().getCurrency().getCurrencyCode());
}
埋め込みプロパティの変換
埋め込み可能なコンポーネント内の特定プロパティを変換する例を見てみましょう。
まず、住所情報を表す埋め込みクラスを定義します:
package com.example.hibernate.entity;
import javax.persistence.Embeddable;
import com.example.hibernate.model.PostalCode;
@Embeddable
public class LocationInfo {
private String streetAddress;
private PostalCode postalCode;
private String cityName;
public LocationInfo() {}
public LocationInfo(String streetAddress, PostalCode postalCode, String cityName) {
this.streetAddress = streetAddress;
this.postalCode = postalCode;
this.cityName = cityName;
}
public String getStreetAddress() {
return streetAddress;
}
public void setStreetAddress(String streetAddress) {
this.streetAddress = streetAddress;
}
public PostalCode getPostalCode() {
return postalCode;
}
public void setPostalCode(PostalCode postalCode) {
this.postalCode = postalCode;
}
public String getCityName() {
return cityName;
}
public void setCityName(String cityName) {
this.cityName = cityName;
}
}
郵便番号を表すクラス階層:
package com.example.hibernate.model;
public abstract class PostalCode {
protected final String code;
protected final String countryCode;
public PostalCode(String code, String countryCode) {
this.code = code;
this.countryCode = countryCode;
}
public String getCode() {
return code;
}
public String getCountryCode() {
return countryCode;
}
}
public class JapanPostalCode extends PostalCode {
public JapanPostalCode(String code) {
super(code, "JP");
}
}
public class USPostalCode extends PostalCode {
public USPostalCode(String code) {
super(code, "US");
}
}
郵便番号用のコンバータ実装:
package com.example.hibernate.converter;
import javax.persistence.AttributeConverter;
import com.example.hibernate.model.PostalCode;
import com.example.hibernate.model.JapanPostalCode;
import com.example.hibernate.model.USPostalCode;
public class PostalCodeConverter implements AttributeConverter<PostalCode, String> {
@Override
public String convertToDatabaseColumn(PostalCode attribute) {
return attribute != null ? attribute.getCode() : null;
}
@Override
public PostalCode convertToEntityAttribute(String dbData) {
if (dbData == null) return null;
if (dbData.matches("\\d{3}-\\d{4}")) {
return new JapanPostalCode(dbData);
} else if (dbData.matches("\\d{5}")) {
return new USPostalCode(dbData);
}
throw new IllegalArgumentException("Unsupported postal code format: " + dbData);
}
}
エンティティでの使用例:
package com.example.hibernate.entity;
import javax.persistence.*;
import com.example.hibernate.converter.PostalCodeConverter;
@Entity
@Table(name = "contacts")
public class Contact {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long contactId;
private String fullName;
@Convert(converter = PostalCodeConverter.class, attributeName = "postalCode")
private LocationInfo location;
// getters and setters
}
attributeName属性を使用することで、埋め込みオブジェクト内の特定のプロパティにコンバータを適用できます。ネストされたパスもサポートされており、例えば「location.city.postalCode」のような階層的な指定も可能です。