ドメインクラス

ドメインクラスはデータベースのカラムを表し、カラムの値をJavaオブジェクトとして処理できるようにします。Domaフレームワークでは、 ドメイン はデータ型に含まれる可能性のあるすべての値を意味します。つまり、ドメインクラスは、カラムにマップできるユーザー定義のクラスです。ドメインクラスの使用はオプションですが、型の安全性のために推奨されます。

すべてのドメインクラスは、内部ドメインクラスまたは外部ドメインクラスのいずれかです。

内部ドメインクラス

内部ドメインクラスには @Domain アノテーションを付ける必要があります。 @DomainvalueType 要素は、対応するデータベースのカラムのデータ型を指定します。valueType 要素には 基本クラス からタイプを指定しなければなりません。

コンストラクタを使用したインスタンス化

@DomainfactoryMethod 要素のデフォルト値は new です。値 new は、アノテーションが付けられたクラスのインスタンスがコンストラクタを使用して作成されることを意味します。

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

    private final String value;

    public PhoneNumber(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }

    public String getAreaCode() {
       ...
    }
}

注釈

レコードには @Domain の代わりに @DataType を使用できます。 @DomainvalueType 要素に対応する情報は、コンストラクタパラメーターの型から解決されます。

@DataType
public record PhoneNumber(String value) {
  public String getAreaCode() {
    ...
  }
}

静的ファクトリメソッドによるインスタンス化

静的ファクトリメソッドを使用してインスタンスを作成するには、@Domain アノテーションの factoryMethod 要素にメソッド名を指定します。

メソッドは静的で非プライベートである必要があります。

@Domain(valueType = String.class, factoryMethod = "of")
public class PhoneNumber {

    private final String value;

    private PhoneNumber(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }

    public String getAreaCode() {
       ...
    }

    public static PhoneNumber of(String value) {
        return new PhoneNumber(value);
    }
}

静的ファクトリメソッドを使用すると、@Domain アノテーションを列挙型に適用できます。

@Domain(valueType = String.class, factoryMethod = "of")
public enum JobType {
    SALESMAN("10"),
    MANAGER("20"),
    ANALYST("30"),
    PRESIDENT("40"),
    CLERK("50");

    private final String value;

    private JobType(String value) {
        this.value = value;
    }

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

    public String getValue() {
        return value;
    }
}

内部ドメインクラスにおける型パラメータの使用

以下に示すように、内部ドメインクラスには型パラメータを含めることができます。

@Domain(valueType = int.class)
public class Identity<T> {

    private final int value;

    public Identity(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}

静的ファクトリメソッドを使用してインスタンスを作成する場合、メソッド宣言にはクラスで宣言された型パラメータと同じものを含める必要があります。

@Domain(valueType = int.class, factoryMethod = "of")
public class Identity<T> {

    private final int value;

    private Identity(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }

    public static <T> Identity<T> of(int value) {
        return new Identity<T>(value);
    }
}

外部ドメインクラス

この機能を使用すると、@Domain アノテーションでクラスに注釈を付けることができなくても、任意のクラスをドメインクラスとして定義できます。

外部ドメインクラスを定義するには、org.seasar.doma.jdbc.domain.DomainConverter を実装するクラスを作成し、@ExternalDomain を注釈する必要があります。

例えば、次のような PhoneNumber クラスがあり、直接修正することはできないとします:

public class PhoneNumber {

    private final String value;

    public PhoneNumber(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }

    public String getAreaCode() {
       ...
    }
}

PhoneNumber クラスを外部ドメインクラスとして定義するには、次のコンバータクラスを作成します。

@ExternalDomain
public class PhoneNumberConverter implements DomainConverter<PhoneNumber, String> {

    public String fromDomainToValue(PhoneNumber domain) {
        return domain.getValue();
    }

    public PhoneNumber fromValueToDomain(String value) {
        if (value == null) {
            return null;
        }
        return new PhoneNumber(value);
    }
}

外部ドメインクラスにおける型パラメータの使用

以下に示すように、外部ドメインクラスも型パラメータを使用できます。

public class Identity<T> {

    private final int value;

    public Identity(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}

DomainConverter 実装クラスで、外部ドメインクラスへの型引数としてワイルドカード ? を使用します。

@ExternalDomain
public class IdentityConverter implements DomainConverter<Identity<?>, String> {

    public String fromDomainToValue(Identity<?> domain) {
        return domain.getValue();
    }

    @SuppressWarnings("rawtypes")
    public Identity<?> fromValueToDomain(String value) {
        if (value == null) {
            return null;
        }
        return new Identity(value);
    }
}

外部ドメインクラスのデータベースへの直接マッピング

すべての外部ドメインクラスは、直接任意のデータベースの型にマッピングできます。

java.util.UUID を PostgreSQL の UUID 型にマッピングする例を次に示します。

まず、マッピングを処理するために org.seasar.doma.jdbc.type.JdbcType の実装を作成します。

class PostgresUUIDJdbcType extends AbstractJdbcType<UUID> {

  protected PostgresUUIDJdbcType() {
    super(Types.OTHER);
  }

  @Override
  protected UUID doGetValue(ResultSet resultSet, int index) throws SQLException {
    String value = resultSet.getString(index);
    return value == null ? null : UUID.fromString(value);
  }

  @Override
  protected void doSetValue(PreparedStatement preparedStatement, int index, UUID value)
      throws SQLException {
    preparedStatement.setObject(index, value, type);
  }

  @Override
  protected UUID doGetValue(CallableStatement callableStatement, int index) throws SQLException {
    String value = callableStatement.getString(index);
    return value == null ? null : UUID.fromString(value);
  }

  @Override
  protected String doConvertToLogFormat(UUID value) {
    return value.toString();
  }
}

次に、org.seasar.doma.it.domain.JdbcTypeProvider を拡張したクラスを作成し、getJdbcType メソッドで上記で作成した JdbcType 実装のインスタンスを返します。

@ExternalDomain
public class PostgresUUIDConverter extends JdbcTypeProvider<UUID> {

  private static final PostgresUUIDJdbcType jdbcType = new PostgresUUIDJdbcType();

  @Override
  public JdbcType<UUID> getJdbcType() {
    return jdbcType;
  }
}

このクラスに @ExternalDomain を注釈することを忘れないでください。

上記のドメインクラスは次のように使用されます。

@Entity
public class Employee {

    @Id
    Identity<Employee> employeeId;

    String employeeName;

    PhoneNumber phoneNumber;

    JobType jobType;

    @Version
    Integer versionNo;

    ...
}
@Dao
public interface EmployeeDao {

    @Select
    Employee selectById(Identity<Employee> employeeId);

    @Select
    Employee selectByPhoneNumber(PhoneNumber phoneNumber);

    @Select
    List<PhoneNumber> selectAllPhoneNumber();

    @Select
    Employee selectByJobType(JobType jobType);

    @Select
    List<JobType> selectAllJobTypes();
}