Domain classes

A domain class represents a table column and allows you to handle column values as Java objects. In the Doma framework, a domain refers to all the values that a data type may contain. In essence, a domain class is a user-defined class that can be mapped to a database column. Using domain classes is optional but recommended for type safety.

Every domain class is either an internal domain class or an external domain class.

Internal domain classes

Internal domain classes must be annotated with @Domain. The valueType element of the @Domain annotation specifies the data type of the corresponding database column. You must specify a type from Basic classes for the valueType element.

Instantiation with a constructor

The default value of the factoryMethod element in the @Domain annotation is new. The value new indicates that instances of the annotated class will be created using a constructor.

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

Note

You can use @DataType instead of @Domain for records. The information corresponding to the valueType element of @Domain is resolved from the type of the constructor parameter.

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

Instantiation with a static factory method

To create instances using a static factory method, specify the method name in the factoryMethod element of the @Domain annotation.

The method must be static and non-private:

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

With a static factory method, you can apply the @Domain annotation to enum types:

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

Using type parameters in internal domain classes

Internal domain classes can include type parameters as shown below:

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

    private final int value;

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

    public int getValue() {
        return value;
    }
}

When creating instances using a static factory method, the method declaration must include the same type parameters as those declared in the class:

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

External domain classes

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

To define external domain classes, you must create a class that implements org.seasar.doma.jdbc.domain.DomainConverter and annotate it with @ExternalDomain.

Consider, for example, the following PhoneNumber class that you cannot modify directly:

public class PhoneNumber {

    private final String value;

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

    public String getValue() {
        return value;
    }

    public String getAreaCode() {
       ...
    }
}

To define the PhoneNumber class as an external domain class, create the following converter class:

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

Using type parameters in external domain classes

External domain classes can also use type parameters, as shown below:

public class Identity<T> {

    private final int value;

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

    public int getValue() {
        return value;
    }
}

In the DomainConverter implementation class, use a wildcard ? as the type argument when referring to the external domain class:

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

Then, create a class that extends org.seasar.doma.it.domain.JdbcTypeProvider. In the getJdbcType method, return an instance of the JdbcType implementation created above:

@ExternalDomain
public class PostgresUUIDConverter extends JdbcTypeProvider<UUID> {

  private static final PostgresUUIDJdbcType jdbcType = new PostgresUUIDJdbcType();

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

Remember to annotate this class with @ExternalDomain.

Example

The Domain classes shown above are used as follows:

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