Я работаю с JPA (реализация Hibernate) в течение некоторого времени, и каждый раз, когда мне нужно создавать сущности, я сталкиваюсь с проблемами как AccessType, неизменяемыми свойствами, equals/hashCode,....
Поэтому я решил попытаться найти общую передовую практику для каждой проблемы и записать ее для личного использования.
Я бы не прочь никому прокомментировать это или сказать, где я ошибаюсь.
Класс сущности
-
реализовать Serializable
Причина: в спецификации указано, что вам нужно, но некоторые поставщики JPA не применяют это. Hibernate как поставщик JPA не применяет это, но он может свалиться где-то глубоко в его желудке с ClassCastException, если Serializable не был реализован.
Конструкторы
-
создать конструктор со всеми обязательными полями объекта
Причина. Конструктор должен всегда оставлять экземпляр, созданный в нормальном состоянии.
-
кроме этого конструктора: иметь конструктор private private по умолчанию
Причина: для конструктора по умолчанию требуется инициализация объекта Hibernate; private разрешено, но для обеспечения генерации прокси-сервера во время выполнения и эффективного извлечения данных без инструментария байт-кодов требуется видимость пакета (или общедоступная).
Поля/Свойства
-
Использовать доступ к полям в общем и доступ к свойствам при необходимости
Причина: это, вероятно, самая спорная проблема, поскольку нет четких и убедительных аргументов для одного или другого (доступ к объектам доступа к объектам); однако доступ к полям кажется общим фаворитом из-за более четкого кода, лучшего инкапсуляции и необходимости создавать сеттеры для неизменяемых полей.
-
Отключить сеттеры для неизменяемых полей (не требуется для поля типа доступа)
- свойства могут быть приватными
Причина: я когда-то слышал, что защита лучше для (Hibernate) производительности, но все, что я могу найти в Интернете: Hibernate может напрямую обращаться к общедоступным, приватным и защищенным методам доступа, а также к открытым, закрытым и защищенным полям. Выбор зависит от вас, и вы можете соответствовать ему в соответствии с вашим дизайном приложения.
Равно/хэш-код
- Никогда не используйте сгенерированный идентификатор, если этот идентификатор установлен только при сохранении объекта
- По желанию: используйте неизменяемые значения для формирования уникального ключа для бизнеса и используйте это для проверки равенства
- Если уникальный бизнес-ключ недоступен, используйте непереходный UUID, который создается, когда объект инициализируется; Подробнее см. эту замечательную статью.
- никогда относятся к связанным объектам (ManyToOne); если этот объект (например, родительский объект) должен быть частью бизнес-ключа, тогда сравнить только идентификатор. Вызов getId() на прокси не приведет к загрузке объекта, если вы используете тип доступа к свойствам.
Пример объекта
@Entity
@Table(name = "ROOM")
public class Room implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
@Column(name = "room_id")
private Integer id;
@Column(name = "number")
private String number; //immutable
@Column(name = "capacity")
private Integer capacity;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "building_id")
private Building building; //immutable
Room() {
// default constructor
}
public Room(Building building, String number) {
// constructor with required field
notNull(building, "Method called with null parameter (application)");
notNull(number, "Method called with null parameter (name)");
this.building = building;
this.number = number;
}
@Override
public boolean equals(final Object otherObj) {
if ((otherObj == null) || !(otherObj instanceof Room)) {
return false;
}
// a room can be uniquely identified by it number and the building it belongs to; normally I would use a UUID in any case but this is just to illustrate the usage of getId()
final Room other = (Room) otherObj;
return new EqualsBuilder().append(getNumber(), other.getNumber())
.append(getBuilding().getId(), other.getBuilding().getId())
.isEquals();
//this assumes that Building.id is annotated with @Access(value = AccessType.PROPERTY)
}
public Building getBuilding() {
return building;
}
public Integer getId() {
return id;
}
public String getNumber() {
return number;
}
@Override
public int hashCode() {
return new HashCodeBuilder().append(getNumber()).append(getBuilding().getId()).toHashCode();
}
public void setCapacity(Integer capacity) {
this.capacity = capacity;
}
//no setters for number, building nor id
}
Другие предложения, которые нужно добавить в этот список, более чем приветствуются...
UPDATE
Считая эту статью, я адаптировал свой способ реализации eq/hC:
- если доступен неизменный простой бизнес-ключ: используйте
- во всех остальных случаях: используйте uuid