ドメインクラス

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

外部ドメインクラス

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

外部ドメインクラスを定義するには、org.seasar.doma.jdbc.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();
}