Я работаю с JPA (реализация Hibernate) уже некоторое время, и каждый раз, когда мне нужно создать сущности, я сталкиваюсь с такими проблемами, как AccessType, неизменяемые свойства, equals / hashCode, ....
Поэтому я решил попытаться найти общие рекомендации по каждому вопросу и записать их для личного использования.
Однако я не против, чтобы кто-то прокомментировал это или сказал мне, где я неправ.
Класс сущности
реализовать Сериализуемый
Причина: в спецификации сказано, что вы должны это сделать, но некоторые провайдеры JPA этого не соблюдают. Hibernate как провайдер JPA не применяет это, но может потерпеть неудачу где-то глубоко в желудке с ClassCastException, если Serializable не был реализован.
Конструкторы
создать конструктор со всеми обязательными полями объекта
Причина: конструктор всегда должен оставлять экземпляр, созданный в нормальном состоянии.
кроме этого конструктора: есть закрытый конструктор по умолчанию для пакета
Причина: конструктор по умолчанию требуется, чтобы 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's 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
}
Другие предложения для добавления в этот список более чем приветствуются ...
ОБНОВИТЬ
После прочтения этой статьи я адаптировал свой способ реализации eq / hC:
- если доступен неизменяемый простой бизнес-ключ: используйте
- во всех остальных случаях: используйте uuid
final
(судя по вашему отсутствию сеттеров, я думаю, вы тоже).notNull
берутся?Ответы:
Я попытаюсь ответить на несколько ключевых моментов: это из долгого опыта Hibernate / постоянства, включая несколько основных приложений.
Класс сущности: реализовать Сериализуемый?
Ключи должны реализовать Serializable. Материал, который будет идти в HttpSession или передаваться по проводам RPC / Java EE, должен реализовывать Serializable. Другие вещи: не так много. Потратьте свое время на то, что важно.
Конструкторы: создать конструктор со всеми обязательными полями объекта?
Конструктор (ы) для логики приложения должен иметь только несколько критических полей «внешний ключ» или «тип / вид», которые всегда будут известны при создании сущности. Остальные должны быть установлены путем вызова методов установки - вот для чего они.
Старайтесь не помещать слишком много полей в конструкторы. Конструкторы должны быть удобными и придавать объектам базовый разум. Имя, тип и / или родители, как правило, полезны.
OTOH, если правила приложения (сегодня) требуют, чтобы у Клиента был адрес, оставьте его для установщика. Это пример "слабого правила". Может быть, на следующей неделе вы хотите создать объект Customer перед тем, как перейти к экрану ввода сведений? Не сбивайте себя с толку, оставляйте возможность для неизвестных, неполных или «частично введенных» данных.
Конструкторы: также, упаковывать закрытый конструктор по умолчанию?
Да, но используйте «защищенный», а не закрытый пакет. Создание подклассов - это настоящая боль, когда необходимые внутренние компоненты не видны.
Поля / Свойства
Используйте доступ к полю 'property' для Hibernate и извне экземпляра. Внутри экземпляра используйте поля напрямую. Причина: позволяет стандартному отражению, простейшему и основному методу Hibernate, работать.
Что касается полей, «неизменяемых» для приложения - Hibernate все еще должен иметь возможность загружать их. Вы можете попытаться сделать эти методы «приватными» и / или добавить к ним аннотацию, чтобы предотвратить нежелательный доступ кода приложения.
Примечание: при написании функции equals () используйте значения get для значений в другом экземпляре! В противном случае вы попадете на неинициализированные / пустые поля в экземплярах прокси.
Защищенный лучше для производительности (Hibernate)?
Вряд ли.
Равно / HashCode?
Это имеет отношение к работе с объектами, прежде чем они будут сохранены - что является сложной проблемой. Хеширование / сравнение по неизменным значениям? В большинстве бизнес-приложений их нет.
Клиент может изменить адрес, изменить название своего бизнеса и т. Д. И т. Д. - не часто, но это случается. Необходимо также внести исправления, если данные были введены неправильно.
Несколько вещей, которые обычно остаются неизменными, это Parenting и, возможно, Type / Kind - обычно пользователь воссоздает запись, а не изменяет ее. Но они не однозначно идентифицируют сущность!
Итак, как бы то ни было, заявленные «неизменяемые» данные на самом деле не являются. Поля первичного ключа / идентификатора создаются для точной цели обеспечения такой гарантированной стабильности и неизменности.
Вам нужно спланировать и рассмотреть свои потребности в этапах сравнения, хеширования и обработки запросов, когда A) работа с «измененными / связанными данными» из пользовательского интерфейса, если вы сравниваете / хэширование «редко меняющихся полей», или B) работа с « несохраненные данные ", если сравнить / хешировать по идентификатору.
Equals / HashCode - если уникальный бизнес-ключ недоступен, используйте непереходный UUID, который создается при инициализации объекта
Да, это хорошая стратегия, когда это необходимо. Имейте в виду, что UUID не являются бесплатными, хотя с точки зрения производительности - и кластеризация усложняет ситуацию.
Equals / HashCode - никогда не ссылаться на связанные объекты
«Если связанный объект (например, родительский объект) должен быть частью бизнес-ключа, то добавьте не вставляемое, не обновляемое поле для хранения родительского идентификатора (с тем же именем, что и ManytoOne JoinColumn) и используйте этот идентификатор в проверке равенства "
Похоже, хороший совет.
Надеюсь это поможет!
источник
Спецификация JPA 2.0 гласит, что:
Спецификация не содержит требований к реализации методов equals и hashCode для сущностей, только для классов первичного ключа и ключей карты, насколько мне известно.
источник
Мои 2 цента в дополнение к ответам здесь:
Что касается доступа к полю или свойству (в отличие от соображений производительности), доступ к ним законным образом осуществляется с помощью методов получения и установки, поэтому моя логика модели может устанавливать / получать их одинаково. Разница проявляется, когда провайдеру персистентной среды выполнения (Hibernate, EclipseLink или другому) необходимо сохранить / установить некоторую запись в таблице A, которая имеет внешний ключ, относящийся к некоторому столбцу в таблице B. В случае типа доступа к свойству, постоянство система времени исполнения использует мой метод кодированного установщика, чтобы присвоить ячейке в столбце таблицы B новое значение. В случае типа доступа к полю система времени выполнения постоянно устанавливает ячейку в столбце таблицы B. Это различие не имеет значения в контексте однонаправленных отношений, тем не менее, НЕОБХОДИМО использовать мой собственный закодированный метод установки (тип доступа к свойству) для двунаправленных отношений, при условии, что метод установки хорошо разработан для учета согласованности. Последовательность является критической проблемой для двунаправленных отношений, обратитесь к этомуссылка на сайт на простой пример для хорошо разработанного сеттера.
Со ссылкой на Equals / hashCode: невозможно использовать автоматически сгенерированные в Eclipse методы Equals / hashCode для объектов, участвующих в двунаправленном отношении, в противном случае они будут иметь циклическую ссылку, что приведет к исключению переполнения стека. После того, как вы попробуете двунаправленное отношение (скажем, OneToOne) и автоматически сгенерируете Equals () или hashCode () или даже toString (), вы попадете в это исключение stackoverflow.
источник
Интерфейс объекта
Базовая реализация для всех сущностей, упрощает реализации Equals / Hashcode:
Номер Entity:
Я не вижу смысла сравнивать равенство сущностей на основе бизнес-полей в каждом случае сущностей JPA. Это может быть больше в случае, если эти сущности JPA рассматриваются как доменные объекты-значения, а не как доменные объекты (для которых предназначены эти примеры кода).
источник