JPA Builder Pattern
TweetPosted on Sunday Oct 16, 2016 at 06:07PM in Technology
Thanks to JPA, creating an entity which has tons of fields and complex relations has become much easier than the plain old JDBC era. but there are still some difficulties with it. for example, consider that you have a database schema which is something like following diagram:
With those entities, let’s say you have to write some code for creating an employee. this would be something like:
public class EmployeeService {
private final EntityManager em;
EmployeeService(final EntityManager em) {
this.em = em;
}
public long create(long deptId,
String name,
boolean temporary,
Set<Long> projectIds,
Set<String> phoneNumbers) {
// instantiating and setting attributes of employee
final Employee employee = new Employee();
employee.setName(name);
employee.setTemporary(temporary);
employee.setProjects(new HashSet<>());
employee.setPhones(new HashSet<>());
// making a relation between employee and dept
final Dept dept = em.find(Dept.class, deptId);
employee.setDept(dept);
em.persist(employee);
dept.getEmployees().add(employee);
// making relations between employee and projects
for (final Long projectId : projectIds) {
final Project project = em.find(Project.class, projectId);
project.getEmployees().add(employee);
employee.getProjects().add(project);
}
// creating phones
for (final String phoneNumber : phoneNumbers) {
final Phone phone = new Phone();
phone.setNumber(phoneNumber);
phone.setEmployee(employee);
em.persist(phone);
employee.getPhones().add(phone);
}
em.flush(); // making sure a generated id is present
return employee.getId();
}
}
And you will use the method create()
as follows:
final Set<Long> projectIds = new HashSet<>();
Collections.addAll(projectIds, project1Id, project2Id);
final Set<String> phoneNumbers = new HashSet<>();
Collections.addAll(phoneNumbers, "000-0000-0001", "000-0000-0002", "000-0000-0003");
final long savedEmployeeId = service.create(
engineeringDeptId,
"Jane Doe",
true,
projectIds,
phoneNumbers);
Not so bad, but think about if there are more complex relations or attributes that may be optional. the arguments of the method will be much longer, and hard to maintain.
In such a case, a pattern which I call "JPA builder pattern" would be nice. you create a non-static nested builder class and a method which creates a builder, into the class EmployeeService
, as follows:
...
public Builder builder(long deptId, String name) {
return new Builder(deptId, name);
}
public final class Builder { // non-static
private final long deptId;
private final String name;
private boolean temporary;
private Set<Long> projectIds = new HashSet<>();
private Set<String> phoneNumbers = new HashSet<>();
private Builder(final long deptId, final String name) {
this.deptId = deptId;
this.name = name;
}
public Builder temporary(boolean temporary) {
this.temporary = temporary;
return this;
}
public Builder projectIds(Long... ids) {
Collections.addAll(projectIds, ids);
return this;
}
public Builder phoneNumbers(String... numbers) {
Collections.addAll(phoneNumbers, numbers);
return this;
}
public long build() {
// In reality, passing "this" instead of actual values (deptId, name, ...) is recommended
return EmployeeService.this.create(deptId, name, temporary, projectIds, phoneNumbers);
}
}
And you will use the builder as follows:
final long savedEmployeeId = service.builder(engineeringDeptId, "Jane Doe")
.temporary(true)
.projectIds(project1Id, project2Id)
.phoneNumbers("000-0000-0001", "000-0000-0002", "000-0000-0003")
.build();
It doesn’t make much sense if relations or attributes that may be optional are not that many as this example, but in reality, entities likely to have those much more. in such a case, I believe this pattern makes your code much clean, readable and maintainable.
You can obtain the entire project which contains entities, the service class and executable tests that run with an embedded database, from my GitHub repo.
Tags: jpa
Why did you implement the service class in the domain class instead of building the builder?
Is it because the domain class is not a singleton?
I wonder why you have implemented it in the service class.
Posted by yun on January 04, 2018 at 07:25 PM JST #