エンティティの型を変更する方法(Doma2)

JavaのORMapperであるDoma2のエンティティはデフォルトでInteger型やString型などの基本型マッピングされる。
ドメインクラスを定義することで、任意のクラスにマッピングさせることができる。

サンプルでは以下のDDLで生成されるテーブルを使用する。

create table sample (
  id VARCHAR(128) not null
  , customer_status_cd CHAR(1) not null
  , name NVARCHAR(30)
  , phone_no VARCHAR(13)
  , constraint sample_PKC primary key (id)
);

なお、基本型のみで定義されるエンティティは以下の通り。

import java.time.LocalDateTime;

import org.seasar.doma.Column;
import org.seasar.doma.Entity;
import org.seasar.doma.Id;
import org.seasar.doma.Table;

import lombok.EqualsAndHashCode;
import lombok.ToString;

@Entity
@Table(name = "sample")
@ToString
@EqualsAndHashCode
public class Sample {

    @Id
    @Column(name = "id")
    String id;

    @Column(name = "customer_status_cd")
    String customerStatusCd;

    @Column(name = "name")
    String name;

    @Column(name = "phone_no")
    String phoneNo;

    // getter, setter 省略
}

ドメインクラスの定義

サンプルのエンティティで、「phoneNo」を任意のクラスにマッピングさせるのを例に、実装方法を記載する。

1. エンティティでマッピングに使用するクラスに@Domainアノテーションを付与する

アノテーションの属性値 valueType には 基本型 を指定する。

@Domain(valueType = String.class)
public class PhoneNumber {
}

2. アノテーションの属性factoryMethodインスタンスを生成するためのメソッド名を指定する

デフォルト値は new であり、privateでないコンストラクタでインスタンスを生成する。
そのため、コンストラクタでインスタンスを生成する場合は属性factoryMethodを省略することが出来る。

// このクラスの場合、publicなコンストラクタでインスタンスを生成可能なため、
// 属性「factoryMethod」は省略可能。
@Domain(valueType = String.class)
public class PhoneNumber {
    
    private String number;
    
    public PhoneNumber(String number) {
        this.number = number;
    }
}

コンストラクタではなく別のメソッドでインスタンスを生成したい場合、
privateでないstaticなファクトリーメソッドを定義し、属性 factoryMethod にそのメソッドの名前を指定する。

@Domain(valueType = String.class, factoryMethod = "getInstance")
public class PhoneNumber {
    
    private String number;
    
    private PhoneNumber(String number) {
        this.number = number;
    }
    
    public static PhoneNumber getInstance(String number) {
        return new PhoneNumber(number);
    }
}

インスタンスを生成するメソッド(もしくはコンストラクタ)を呼ぶ上で、
引数がnullであることを許容する場合は、属性 acceptNull にtrueを設定する(デフォルト:false)。

@Domain(valueType = String.class, factoryMethod = "getInstance", acceptNull = true)
public class PhoneNumber {
    
    private String number;
    
    private PhoneNumber(String number) {
        this.number = number;
    }
    
    public static PhoneNumber getInstance(String number) {
        return new PhoneNumber(number);
    }
}

3. アノテーションの属性accessorMethodでラップする値を取得するためのメソッド名を指定する

デフォルト値はgetValueである。メソッド名を変更したい場合は属性accessorMethodにメソッド名を指定する。
ここで指定するメソッドもprivateでないものとする。
accessorMethodに指定したメソッドで返却される値は、DBから取得される値となるものとなるようにメソッドを指定する。

@Domain(valueType = String.class, factoryMethod = "getInstance", accessorMethod = "getNumber")
public class PhoneNumber {
    
    private String number;
    
    private PhoneNumber(String number) {
        this.number = number;
    }
    
    public static PhoneNumber getInstance(String number) {
        return new PhoneNumber(number);
    }
    
    public String getNumber() {
        return number;
    }
}

ドメインクラスはクラスだけでなくenumでも定義することが出来る。
以下はenumの定義例である。

import org.seasar.doma.Domain;

@Domain(valueType = String.class, factoryMethod = "of")
public enum CustomerStatusCode {
    BLANK("1"),
    FILLED("2");

    private final String value;

    CustomerStatusCode(String value) {
        this.value = value;
    }

    public static CustomerStatusCode of(String value) {
        for (CustomerStatusCode code: CustomerStatusCode.values()) {
            if (code.value.equals(value)) {
                return code;
            }
        }
        throw new IllegalArgumentException(value);
    }

    public String getValue() {
        return value;
    }
}

②エンティティの型を変更する

エンティティのフィールドの型を、作成したドメインクラスの型に変更する。

import java.time.LocalDateTime;

import org.seasar.doma.Column;
import org.seasar.doma.Entity;
import org.seasar.doma.Id;
import org.seasar.doma.Table;

import lombok.EqualsAndHashCode;
import lombok.ToString;

@Entity
@Table(name = "sample")
@ToString
@EqualsAndHashCode
public class Sample {

    @Id
    @Column(name = "id")
    String id;

    @Column(name = "customer_status_cd")
    CustomerStatusCode customerStatusCd;

    @Column(name = "name")
    String name;

    @Column(name = "phone_no")
    PhoneNumber phoneNo;

    // getter, setter 省略
}

ドメインクラスを使用したエンティティでのクエリの書き方

サンプルで使用するDaoインターフェースは以下の通り。
エンティティは上記で作成したものを使用する。

@ConfigAutowireable
@Dao
public interface SampleDao {

    @Select
    List<Sample> selectBySample(Sample entity);
}

ドメインクラスを使用したエンティティでのselect文の記述例を示す。

select
  /*%expand*/*
from
  sample
where
  customer_status_cd = /* entity.customerStatusCd */'3'

※2023/03/05追記

自動生成時にもマッピングさせる

doma-codegen-pluginを使用して、GradleタスクでDaoやEntityを自動生成する方法は以下の記事を参照。

olafnosuke.hatenablog.com

Entityの自動生成時にも任意のカラムを任意のクラスにマッピングさせたい場合は、以下の設定を追加する。

entityPropertyClassNamesFileを設定する

1. build.gradleのentityにentityPropertyClassNamesFileを設定する

entity {
    entityPropertyClassNamesFile = file("$projectDir/codegen-enum-mapping.properties")
 }

2. codegen-enum-mapping.propertiesを作成し、Entityのマッピングの定義を記述する。

  • key ... Entityのフルパッケージ+"@"+メンバ変数
  • value ... 作成したドメインクラスのフルパッケージ
codegen-enum-mapping.propertiesのサンプル
jp.co.sample.persistence.entity.Sample@customerStatusCd=jp.co.sample.constant.CustomerStatusCode