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.
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 basic
to the valueType
element.
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.
@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
:
@Domain(valueType = String.class, accessorMethod = "value")
public record PhoneNumber(String value) {
public String getAreaCode() {
...
}
}
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
:
@DataType
public record PhoneNumber(String value) {
public String getAreaCode() {
...
}
}
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:
@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;
}
}
All internal domain class declarations have type parameters:
@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 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:
@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 arbitrary classes as domain classes, even if the classes can be annotated with the @Domain
annotation.
To define external domain classes, you have to do as follows:
- Create a class that implements
org.seasar.doma.jdbc.domain.DomainConverter
and annotate@ExternalDomain
to the class - Create a class that is annotated with
@DomainConverters
- Specify the class annotated with
@ExternalDomain
to the@DomainConverters
'svalue
element - Specify the full qualified name of the class annotated with
@DomainConverters
to the option ofannotation-processing
Suppose, for instance, there is the PhoneNumber
class that you can change:
public class PhoneNumber {
private final String value;
public PhoneNumber(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public String getAreaCode() {
...
}
}
First, to define the PhoneNumber
class as an external domain class, create following 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);
}
}
Then create following class and specify the above class to the @DomainConverters
's value
element:
@DomainConverters({ PhoneNumberConverter.class })
public class DomainConvertersProvider {
}
Finally, specify the full qualified name of the above class to the option of annotation-processing
. If you use Gradle, specify the option in the build script as follows:
compileJava {
options {
compilerArgs = ['-Adoma.domain.converters=example.DomainConvertersProvider']
}
}
All external domain class declarations have type parameters:
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, specify a wildcard ?
as type arguments 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);
}
}
The Domain classes showed 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();
}