============== Domain classes ============== .. contents:: :depth: 3 A domain class represents a table column and it allows you to handle the column value as a Java object. In the Doma framework, a **domain** means all the values which a data type may contain. In short, a domain class is a user defined class that can be map to a column. The use of the domain classes is optional. Every domain class is either an internal domain class or an external domain class. Internal domain classes ======================= The internal domain class must be annotated with ``@Domain``. The ``@Domain``'s ``valueType`` element corresponds to a data type of a column. Specify any type of :doc:`basic` to the ``valueType`` element. Instantiation with a constructor -------------------------------- The default value of the ``@Domain``'s ``factoryMethod`` element is ``new``. The value ``new`` means that the object of annotated class is created with a constructor. .. code-block:: java @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:: In Java 14 and later version, you can annotate `records`_ with ``@Domain``: .. code-block:: java @Domain(valueType = String.class, accessorMethod = "value") public record PhoneNumber(String value) { public String getAreaCode() { ... } } .. _records: https://openjdk.java.net/jeps/359 .. warning:: To annotate records with ``@Domain`` is a little redundant, because you must specify some properties to ``@Domain`` such as ``valueType``. Instead of ``@Domain``, you can annotate records with ``@DataType``: .. code-block:: java @DataType public record PhoneNumber(String value) { public String getAreaCode() { ... } } Instantiation with a static factory method ------------------------------------------ To create the object of annotated class with a static factory method, specify the method name to the ``@Domain``'s ``factoryMethod`` element. The method must be static and non-private: .. code-block:: java @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: .. code-block:: java @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 ------------------------------------------------ All internal domain class declarations have type parameters: .. code-block:: java @Domain(valueType = int.class) public class Identity { private final int value; public Identity(int value) { this.value = value; } public int getValue() { return value; } } When you create the object of annotated class with a static factory method, the method declaration must have same type parameters that are declared in the class declaration: .. code-block:: java @Domain(valueType = int.class, factoryMethod = "of") public class Identity { private final int value; private Identity(int value) { this.value = value; } public int getValue() { return value; } public static Identity of(int value) { return new Identity(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 have to create a class that implements ``org.seasar.doma.jdbc.domain.DomainConverter`` and annotate ``@ExternalDomain`` to the class. Suppose, for instance, there is the ``PhoneNumber`` class that you can change: .. code-block:: java 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 following class: .. code-block:: java @ExternalDomain public class PhoneNumberConverter implements DomainConverter { 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 ------------------------------------------------ All external domain class declarations have type parameters: .. code-block:: java public class Identity { private final int value; public Identity(int value) { this.value = value; } public int getValue() { return value; } } In the ``DomainConverter`` implementation class, specify a wildcard ``?`` as type arguments to the external domain class: .. code-block:: java @ExternalDomain public class IdentityConverter implements DomainConverter, 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: .. code-block:: java class PostgresUUIDJdbcType extends AbstractJdbcType { 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``, and in the ``getJdbcType`` method, return an instance of the ``JdbcType`` implementation created above: .. code-block:: java @ExternalDomain public class PostgresUUIDConverter extends JdbcTypeProvider { private static final PostgresUUIDJdbcType jdbcType = new PostgresUUIDJdbcType(); @Override public JdbcType getJdbcType() { return jdbcType; } } Don't forget to annotate the class with ``@ExternalDomain``. Example ======= The Domain classes showed above are used as follows: .. code-block:: java @Entity public class Employee { @Id Identity employeeId; String employeeName; PhoneNumber phoneNumber; JobType jobType; @Version Integer versionNo(); ... } .. code-block:: java @Dao public interface EmployeeDao { @Select Employee selectById(Identity employeeId); @Select Employee selectByPhoneNumber(PhoneNumber phoneNumber); @Select List selectAllPhoneNumber(); @Select Employee selectByJobType(JobType jobType); @Select List selectAllJobTypes(); }