Создание идеальной сущности JPA [закрыто]

Я уже некоторое время работаю с JPA (реализация Hibernate), и каждый раз, когда мне нужно создавать сущности, я борюсь с проблемами как AccessType, неизменяемые свойства, equals/hashCode,... .
Поэтому я решил попытаться выяснить общую наилучшую практику для каждого вопроса и записать это для личного использования.
Однако я бы не возражал, чтобы кто-нибудь прокомментировал это или сказал мне, где я ошибаюсь.

сущность Класс!--6-->
  • реализовать Serializable

    причина: спецификация говорит, что вы должны, но некоторые поставщики JPA не применяют это. Hibernate как поставщик JPA не применяет это, но он может потерпеть неудачу где-то глубоко в желудке с ClassCastException, если сериализуемый не был реализован.

конструкторы

  • создайте конструктор со всеми обязательными полями сущности

    причина: конструктор всегда должен оставлять экземпляр, созданный в нормальном состоянии.

  • кроме этого конструктора: имейте конструктор по умолчанию пакета

    причина: конструктор по умолчанию требуется, чтобы Hibernate инициализировал сущность; private разрешен, но для генерации прокси-сервера среды выполнения и эффективного извлечения данных без байт-кода требуется частная (или общедоступная) видимость пакета аппаратура.

Поля/Свойства

  • используйте доступ к полю в целом и доступ к свойствам, когда это необходимо

    причина: это, вероятно, самый спорный вопрос, поскольку нет четких и убедительных аргументов для одного или другого (доступ к свойствам против доступа к полю); однако доступ к полю кажется общим фаворитом из-за более четкого кода, лучшей инкапсуляции и нет необходимости создавать сеттеры для неизменяемые поля

  • опустить сеттеры для неизменяемых полей (не требуется для поля типа доступа)

  • свойства могут быть частными
    Причина: я когда-то слышал, что protected лучше для производительности (Hibernate), но все, что я могу найти в интернете: Hibernate может напрямую обращаться к публичным, частным и защищенным методам доступа, а также к публичным, частным и защищенным полям. Выбор за вами, и вы можете соответствовать ему, чтобы соответствовать вашим дизайн приложения.

Equals / hashCode

  • никогда не используйте сгенерированный идентификатор, если этот идентификатор установлен только при сохранении сущности
  • по предпочтению: используйте неизменяемые значения для формирования уникального бизнес-ключа и используйте его для проверки равенства
  • если уникальный бизнес-ключ недоступен, используйте нестационарный UUID который создается при инициализации сущности; см. эту большую статью дополнительные информация.
  • никогда обратитесь к связанным сущностям (ManyToOne); если эта сущность (например, родительская сущность) должна быть частью бизнес-ключа, сравните только ID. Вызов 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

4 ответов


на спецификация JPA 2.0 гласит:

  • класс сущности должен иметь конструктор no-arg. У него могут быть и другие конструкторы. Конструктор no-arg должен быть общедоступным или защищенным.
  • класс сущности должен быть топ-класса. Перечисление или интерфейс не должны быть обозначается как сущность.
  • класс сущности не должен быть окончательным. Никакие методы или постоянные переменные экземпляра класса entity не могут быть окончательный.
  • если экземпляр сущности должен передаваться по значению как отдельный объект (например, через удаленный интерфейс), класс сущности должен реализовывать интерфейс Serializable.
  • как абстрактные, так и конкретные классы могут быть сущностями. Сущности могут расширять как классы сущностей, так и классы сущностей, а классы сущностей могут расширять классы сущностей.

спецификация не содержит требований об реализация методов equals и hashCode для сущностей, только для классов первичных ключей и ключей карты, насколько я знаю.


Я попытаюсь ответить на несколько ключевых моментов: это из длительного спящего режима/ персистентности, включая несколько основных приложений.

организация класса: реализовать Serializable?

ключи должен реализовать Serializable. Материал, который будет идти в HttpSession, или будет отправлен по проводу RPC/Java EE, должен реализовать сериализуемый. Другие вещи: не так много. Тратить свое время на то, что важный.

конструкторы: создать конструктор со всеми необходимыми полями сущности?

конструктор(ы) для логики приложения должен иметь только несколько критических полей "внешний ключ" или "тип/вид", которые всегда будут известны при создании объекта. Остальные должны быть установлены путем вызова методов setter - вот для чего они предназначены.

избегайте слишком много полей в конструкторы. Конструкторы должны быть удобными, и давать основное здравомыслие к объекту. Имя, тип и/или родители, как правило, полезны.

OTOH если правила применения (сегодня) требуют, чтобы клиент имел адрес, оставьте это сеттеру. Это пример "слабого правила". Может быть, на следующей неделе вы хотите создать объект клиента, прежде чем перейти к экрану ввода сведений? Не спотыкайтесь, не оставляйте возможности для неизвестных, неполных или" частично введенных " данных.

конструкторы: кроме того, пакет private default конструктор?

да, но используйте "защищенный", а не пакет private. Подклассы - это настоящая боль, когда необходимые внутренние органы не видны.

Поля/Свойства

используйте доступ к полю "свойство" для спящего режима и извне экземпляра. В экземпляре используйте поля напрямую. Причина: позволяет работать стандартному отражению, простейшему и самому основному методу для спящего режима.

Что касается полей 'immutable' to приложение -- Hibernate по-прежнему должно иметь возможность загружать их. Вы можете попробовать сделать эти методы "частными" и / или поместить на них аннотацию, чтобы предотвратить нежелательный доступ к коду приложения.

Примечание: при написании функции equals() используйте геттеры для значений в экземпляре "other"! В противном случае вы нажмете неинициализированные/ пустые поля в экземплярах прокси-сервера.

Protected лучше для (спящий режим) представление?

вряд ли.

Equals / HashCode?

Это имеет отношение к работе с сущностями, прежде чем они были спасены-это сложный вопрос. Хеширование / сравнение по неизменяемым значениям? В большинстве бизнес-приложений их нет.

клиент может изменить адрес, изменить название своего бизнеса и т. д. -- Не часто, но случается. Исправления также должны быть возможны, когда данные не были введены правильно.

несколько вещей, которые обычно хранятся неизменяемыми, являются родительство и, возможно, тип / вид-обычно пользователь воссоздает запись, а не изменяет их. Но они не однозначно идентифицируют сущность!

Итак, длинные и короткие, заявленные "неизменяемые" данные на самом деле не являются. Поля первичного ключа/ ID генерируются с точной целью обеспечения такой гарантированной стабильности и неизменности.

вам нужно запланировать & рассмотреть вашу потребность для сравнения & хеширования & обработки запросов этапы работ при а) работа с "изменить/ связанных данных" от интерфейса, если вы сравните/окрошка на "неизменяемый поля", или Б) работа с "несохраненные данные", если сравнивать хэш по ID.

Equals / HashCode -- если уникальный бизнес-ключ недоступен, используйте нестационарный UUID, который создается при инициализации сущности

Да, это хорошая стратегия, когда требуется. Имейте в виду, что UUIDs не являются бесплатными, с точки зрения производительности хотя и кластеризация усложняет.

Equals / HashCode -- никогда не ссылаться на связанные сущности

"Если Связанный объект (например, родительский объект) должен быть частью бизнес-ключа, добавьте не вставляемое, не обновляемое поле для хранения родительского идентификатора (с тем же именем, что и Manytoone JoinColumn) и используйте этот идентификатор в проверке равенства"

звучит, как хороший совет.

надеюсь, что это помогает!


мои 2 цента дополнение к ответам здесь:

  1. со ссылкой на доступ к полю или свойству (вдали от соображений производительности) оба имеют законный доступ с помощью геттеров и сеттеров, таким образом, моя логика модели может установить/получить их таким же образом. Разница возникает, когда поставщику среды выполнения персистентности (Hibernate, EclipseLink или еще) необходимо сохранить / установить некоторую запись в таблице A, которая имеет внешний ключ, ссылающийся на некоторый столбец в таблице B. В случае типа доступа к свойству система выполнения persistence использует метод my coded setter для присвоения ячейке в столбце таблицы B нового значения. В случае типа доступа к полю система среды выполнения сохраняемости задает ячейку непосредственно в столбце таблицы B. Это различие не имеет значения в контексте однонаправленного отношения, но необходимо использовать мой собственный метод кодированного сеттера (тип доступа к свойствам) для двунаправленного отношения при условии, что метод сеттера хорошо разработан для счета для единообразия. Согласованность является критической проблемой для двунаправленных отношений обратитесь к этому ссылке простой пример хорошо продуманной сеттер.

  2. со ссылкой на Equals/hashCode: невозможно использовать методы Eclipse auto-generated Equals / hashCode для сущностей, участвующих в двунаправленных отношениях, иначе они будут иметь круговую ссылку, приводящую к исключению stackoverflow. Как только вы попробуете двунаправленный отношение (скажем, OneToOne) и автоматическое создание Equals () или hashCode () или даже toString () вы попадете в это исключение stackoverflow.


сущность интерфейс

public interface Entity<I> extends Serializable {

/**
 * @return entity identity
 */
I getId();

/**
 * @return HashCode of entity identity
 */
int identityHashCode();

/**
 * @param other
 *            Other entity
 * @return true if identities of entities are equal
 */
boolean identityEquals(Entity<?> other);
}

базовая реализация для всех сущностей, упрощает реализацию Equals/Hashcode:

public abstract class AbstractEntity<I> implements Entity<I> {

@Override
public final boolean identityEquals(Entity<?> other) {
    if (getId() == null) {
        return false;
    }
    return getId().equals(other.getId());
}

@Override
public final int identityHashCode() {
    return new HashCodeBuilder().append(this.getId()).toHashCode();
}

@Override
public final int hashCode() {
    return identityHashCode();
}

@Override
public final boolean equals(final Object o) {
    if (this == o) {
        return true;
    }
    if ((o == null) || (getClass() != o.getClass())) {
        return false;
    }

    return identityEquals((Entity<?>) o);
}

@Override
public String toString() {
    return getClass().getSimpleName() + ": " + identity();
    // OR 
    // return ReflectionToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
}
}

Room Entity impl:

@Entity
@Table(name = "ROOM")
public class Room extends AbstractEntity<Integer> {

private static final long serialVersionUID = 1L;

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@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;
}

public Integer getId(){
    return id;
}

public Building getBuilding() {
    return building;
}

public String getNumber() {
    return number;
}


public void setCapacity(Integer capacity) {
    this.capacity = capacity;
}

//no setters for number, building nor id
}

Я не вижу смысла сравнивать равенство сущностей на основе бизнес-полей в каждом случае сущностей JPA. Это может быть больше, если эти сущности JPA рассматриваются как управляемые доменом ValueObjects, а не управляемые доменом сущности (для которых предназначены эти примеры кода).