ドメインクラス

ドメインクラスはデータベースのカラムを表し、カラムの値を 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() {
       ...
    }
}

注釈

Java 14 以降のバージョンでは、records@Domain アノテーションを付けることができます。

@Domain(valueType = String.class, accessorMethod = "value")
public record PhoneNumber(String value) {
  public String getAreaCode() {
    ...
  }
}

警告

レコードに @Domain アノテーションを付けるのは少し冗長です。 valueType などのいくつかのプロパティを明示的に @Domain に指定する必要があるためです。 @Domain の代わりに、レコードに @DataType アノテーションを付けることができます。

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

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

アノテーションが付与されたクラスのオブジェクトを静的ファクトリメソッドで作成するには、 @DomainfactoryMethod 要素にメソッド名を指定します。

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

@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);
    }
}

外部ドメインクラス

This feature allows you to define any class as a domain class, even if you cannot annotate the class with the @Domain annotation.

外部ドメイン クラスを定義するには、次のようにします。

  • org.seasar.Doma.jdbc.domain.DomainConverter を実装するクラスを作成し、クラスに @ExternalDomain アノテーションを付ける

  • @DomainConverters アノテーションを付けたクラスを作成する

  • @DomainConvertersvalue 要素に @ExternalDomain アノテーションを付けたクラスを指定する

  • アノテーションプロセッシング のオプションに @DomainConverters アノテーションを付けたクラスの完全修飾名を指定する

たとえば、ソースコードを変更でない 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);
    }
}

次に、次のクラスを作成し、上記のクラスを @DomainConvertersvalue 要素に指定します。

@DomainConverters({ PhoneNumberConverter.class })
public class DomainConvertersProvider {
}

最後に、アノテーションプロセッシング のオプションに上記クラスの完全修飾名を指定します。 Gradle を使用する場合は、ビルド スクリプトで次のようにオプションを指定します。

compileJava {
    options {
        compilerArgs = ['-Adoma.domain.converters=example.DomainConvertersProvider']
    }
}

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

外部ドメインクラスには任意の数の型パラメータを宣言できます。

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);
    }
}

Direct mapping of external domain classes to the database

All external domain classes can be directly mapped to any database type.

Here's an example of mapping java.util.UUID to PostgreSQL's UUID type.

First, create an implementation of org.seasar.doma.jdbc.type.JdbcType to handle the mapping:

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();
  }
}

Next, create a class that extends org.seasar.doma.it.domain.JdbcTypeProvider, and in the getJdbcType method, return an instance of the JdbcType implementation created above. Don't forget to annotate the class with @ExternalDomain:

@ExternalDomain
public class PostgresUUIDConverter extends JdbcTypeProvider<UUID> {

  private static final PostgresUUIDJdbcType jdbcType = new PostgresUUIDJdbcType();

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

The rest follows the standard approach. Simply add PostgresUUIDConverter to the @DomainConverters annotation, and specify the fully qualified name of the class with the @DomainConverters annotation in the annotation processing options.

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

@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();
}