Hibernateにおけるカスタムコンバータの実装方法

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」のような階層的な指定も可能です。

タグ: Hibernate jpa AttributeConverter Java ORM

8月4日 03:21 投稿