В нашем проекте мы используем подход vanilla builders (@see Effective Java). Рассмотрим следующий пример:
@Entity
public class Person {
public static class Builder {
private String firstName;
private String lastName;
private PhoneNumber phone;
public Builder() {}
public Builder withFullName(String fullName) {
Preconditions.notNull(fullName);
String[] split = fullName.split(" ");
if (split == null || split.length != 2) {
throw new IllegalArgumentException("Full name should contain First name and Last name. Full name: " + fullName);
}
this.firstName = split[0];
this.lastName = split[1];
return this;
}
public Builder withPhone(String phone) {
// valueOf does validation
this.phone = PhoneNumber.valueOf(phone);
return this;
}
public Person build() {
return new Person(this);
}
}
//@Columns
private long id;//@Id
private String firstName;
private String lastName;
private String phoneNumber;
// hibernate requires default constructor
private Person() {}
private Person(Builder builder) {
this.firstName = Preconditions.notNull(builder.firstName);
this.lastName = Preconditions.notNull(builder.lastName);
this.phoneNumber = builder.phone != null ? builder.phone : null;
}
//Getters
@Nonnull
public String getFirstName() { return firstName;}
@Nonnull
public String getLastName() { return lastName;}
@Nullable
public String getPhoneName() { return phone;}
public long getId() { return id;}
}
На случай, если вы захотите иногда видоизменить объект, я рекомендую ввести new Builder(Person person)
, который скопирует все данные обратно, чтобы вы могли преобразовать его с помощью компоновщика. Конечно, он создаст новую сущность, поэтому старая останется только для чтения.
Использование (с мутацией) так же просто, как:
Person.Builder personBuilder = new Person.Builder();
Person person = personBuilder.withFullName("Vadim Kirilchuk").withPhone("12345678").build();
Person modified = new Person.Builder(person).withPhone("987654321").build();
Также важно отметить, что в этом примере Person не является на 100% неизменным (и не может быть) классом: прежде всего потому, что jpa устанавливает id , также могут быть ленивые ассоциации извлекается во время выполнения и, наконец, потому что у вас не может быть полей final (из-за необходимого конструктора по умолчанию) :( Последний момент также относится к многопоточным средам, то есть возможно, что сущность, переданная в другой поток сразу после привести к всевозможным ошибкам, так как другой поток не гарантирует увидеть полностью построенный объект.
Спецификация JPA 2.1, раздел «2.1. Класс сущности», гласит:
Запрещается использование методов или постоянных переменных экземпляра класса сущностей.
окончательный.
Еще один похожий подход: http://vlkan.com/blog/post/2015/03/21/immutable-persistence/
В моем случае я бы просто добавил id для строителя, а не создавал службу поверх черновиков ..