Hibernate Аннотации - Что лучше, доступ к полю или собственности? - PullRequest
124 голосов
/ 27 февраля 2009

Этот вопрос имеет некоторое отношение к Вопросу размещения аннотации в спящем режиме .

Но я хочу знать, что лучше ? Доступ через свойства или доступ через поля? Каковы преимущества и недостатки каждого?

Ответы [ 25 ]

232 голосов
/ 22 мая 2011

Есть аргументы для обоих, но большинство из них проистекают из определенных пользовательских требований "что делать, если вам нужно добавить логику для", или "инкапсуляция xxxx нарушает". Тем не менее, никто действительно не прокомментировал теорию и дал должным образом аргументированный аргумент.

Что на самом деле делает Hibernate / JPA, когда сохраняет объект - ну, он сохраняет состояние объекта. Это означает, что хранить его так, чтобы его можно было легко воспроизвести.

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

Думайте об этом как MS Word. MS Word поддерживает модель документа в памяти - документы STATE. Он представляет интерфейс, который пользователь может использовать для изменения документа - набор кнопок, инструментов, команд клавиатуры и т. Д. Однако, когда вы решите сохранить (сохранить) этот документ, он сохраняет внутреннее состояние, а не набор нажатий клавиш и щелчки мыши, используемые для его генерации.

Сохранение внутреннего состояния объекта НЕ нарушает инкапсуляцию - иначе вы не совсем понимаете, что такое инкапсуляция и почему она существует. Это действительно похоже на сериализацию объектов.

По этой причине, В БОЛЬШИНСТВЕ СЛУЧАЙ, уместно сохранять ПОЛЯ, а не АКСЕССУАРЫ. Это означает, что объект может быть точно воссоздан из базы данных точно так, как он был сохранен. Он не должен нуждаться в проверке, потому что это было сделано на оригинале, когда он был создан, и до того, как он был сохранен в базе данных (если, не дай бог, вы храните недопустимые данные в БД !!!!). Аналогично, не нужно рассчитывать значения, так как они уже были рассчитаны до сохранения объекта. Объект должен выглядеть так же, как и до сохранения. Фактически, добавляя дополнительные элементы в методы получения / установки, вы фактически увеличиваете риск того, что вы воссоздаете что-то, что не является точной копией оригинала.

Конечно, эта функциональность была добавлена ​​по причине. Могут быть некоторые допустимые варианты использования для сохранения методов доступа, однако они обычно бывают редкими. Примером может быть то, что вы хотите избежать сохранения вычисляемого значения, хотя вы можете задать вопрос, почему вы не вычисляете его по требованию в получателе значения или лениво инициализируете его в получателе. Лично я не могу придумать ни одного хорошего варианта использования, и ни один из ответов здесь действительно не дает ответа «Разработка программного обеспечения».

76 голосов
/ 27 февраля 2009

Я предпочитаю доступ к полям, потому что таким образом я не обязан предоставлять getter / setter для каждого свойства.

Быстрый опрос через Google показывает, что доступ к полям составляет большинство (например, http://java.dzone.com/tips/12-feb-jpa-20-why-accesstype).

Я считаю, что доступ к полю - это идиома, рекомендованная Spring, но я не могу найти ссылку, подтверждающую это.

Есть связанный вопрос SO , который пытался измерить производительность и пришел к выводу, что "нет разницы".

37 голосов
/ 06 июля 2010

Вот ситуация, когда вы должны использовать средства доступа к свойствам. Представьте, что у вас есть абстрактный класс GENERIC с большим количеством реализаций для наследования в 8 конкретных подклассов:

public abstract class Foo<T extends Bar> {

    T oneThing;
    T anotherThing;

    // getters and setters ommited for brevity

    // Lots and lots of implementation regarding oneThing and anotherThing here
 }

Теперь, как именно вы должны аннотировать этот класс? Ответ: ВЫ НЕ МОЖЕТЕ аннотировать его с помощью доступа к полю или свойству, потому что вы не можете указать целевую сущность на этом этапе. Вы должны аннотировать конкретные реализации. Но поскольку постоянные свойства объявлены в этом суперклассе, вы ДОЛЖНЫ использовать доступ к свойствам в подклассах.

Доступ к полю недоступен в приложении с абстрактными обобщенными суперклассами.

33 голосов
/ 20 мая 2010

Я предпочитаю использовать свойства доступа:

  • Я могу добавить логику, если возникнет такая необходимость (как указано в принятом ответе).
  • это позволяет мне звонить foo.getId() без инициализации прокси (важно при использовании Hibernate, пока не будет разрешено HHH-3718 ).

Минус:

  • это делает код менее читабельным, вам нужно, например, просмотреть весь класс, чтобы увидеть, есть ли там @Transient.
31 голосов
/ 16 апреля 2009

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

@Entity
public class Person {

  @Column("nickName")
  public String getNickName(){
     if(this.name != null) return generateFunnyNick(this.name);
     else return "John Doe";
  }
}

Кроме того, если вы добавите в микс еще одну библиотеку (например, какую-нибудь JSON-конвертирующую библиотеку или BeanMapper или Dozer или другую библиотеку для отображения / клонирования bean-компонентов на основе свойств getter / setter), у вас будет гарантия, что библиотека синхронизирована с диспетчером персистентности (оба используют метод получения / установки).

13 голосов
/ 27 февраля 2009

Это действительно зависит от конкретного случая - оба варианта доступны по причине. ИМО сводится к трем случаям:

  1. setter имеет некоторую логику, которая не должна выполняться во время загрузки экземпляра из базы данных; например, некоторая проверка значения происходит в установщике, однако данные, поступающие из базы данных, должны быть действительными (в противном случае они туда не попадут (:); в этом случае доступ к полю является наиболее подходящим;
  2. setter имеет некоторую логику, которая всегда должна вызываться, даже во время загрузки экземпляра из db; например, инициализируемое свойство используется при вычислении некоторого вычисляемого поля (например, свойство - денежная сумма, вычисляемое свойство - всего несколько денежных свойств одного и того же экземпляра); в этом случае требуется доступ к свойству.
  3. Ни один из вышеперечисленных случаев - тогда оба варианта применимы, просто оставайтесь последовательными (например, если доступ к полю является выбором в этой ситуации, используйте его все время в аналогичной ситуации).
12 голосов
/ 26 июля 2012

Я бы настоятельно рекомендовал доступ к полям, а НЕ аннотации к геттерам (доступ к свойствам), если вы хотите сделать в установщиках нечто большее, чем просто установка значения (например, шифрование или вычисление).

Проблема с доступом к свойству заключается в том, что сеттеры также вызываются при загрузке объекта. У меня это работало много месяцев, пока мы не захотели ввести шифрование. В нашем случае мы хотели зашифровать поле в установщике и расшифровать его в получателе. Проблема с доступом к свойству заключалась в том, что когда Hibernate загружал объект, он также вызывал установщик для заполнения поля и, таким образом, снова шифровал зашифрованное значение. Этот пост также упоминает это: Java Hibernate: поведение функции набора различных свойств в зависимости от того, кто ее вызывает

Это вызывало у меня головную боль, пока я не вспомнил разницу между доступом к полю и доступом к свойству. Теперь я переместил все свои аннотации из доступа к свойству в доступ к полю, и теперь он работает нормально.

8 голосов
/ 12 марта 2015

Я предпочитаю использовать доступ к полям по следующим причинам:

  1. Доступ к свойству может привести к очень неприятным ошибкам при реализации equals / hashCode и ссылок на поля непосредственно (в отличие от их получателей). Это связано с тем, что прокси-сервер инициализируется только при обращении к геттерам, а прямой доступ к полю просто возвращает ноль.

  2. Для доступа к свойству необходимо аннотировать все служебные методы (например, addChild / removeChild) как @Transient.

  3. При доступе к полю мы можем скрыть поле @Version, вообще не открывая геттер. Получатель также может привести к добавлению установщика, и поле version никогда не следует устанавливать вручную (что может привести к очень неприятным проблемам). Все приращения версии должны запускаться через OPTIMISTIC_FORCE_INCREMENT или PESSIMISTIC_FORCE_INCREMENT явная блокировка.

7 голосов
/ 23 апреля 2009

Я думаю, что аннотирование свойства лучше, потому что обновление полей напрямую нарушает инкапсуляцию, даже когда ваш ORM делает это.

Вот отличный пример того, как он вас сожжет: вы, вероятно, хотите, чтобы ваши аннотации для средства проверки и сохранения в спящем режиме находились в одном месте (в полях или свойствах). Если вы хотите проверить свои валидированные валидаторы, которые аннотированы на поле, вы не можете использовать макет вашей сущности, чтобы изолировать свой модульный тест только от валидатора. Уч.

6 голосов
/ 27 февраля 2009

Я полагаю, что доступ к свойству и доступ к полю слегка отличаются в отношении отложенной инициализации.

Рассмотрим следующие отображения для 2 основных бинов:

<hibernate-mapping package="org.nkl.model" default-access="field">
  <class name="FieldBean" table="FIELD_BEAN">
    <id name="id">
      <generator class="sequence" />
    </id>
    <property name="message" />
  </class>
</hibernate-mapping>

<hibernate-mapping package="org.nkl.model" default-access="property">
  <class name="PropBean" table="PROP_BEAN">
    <id name="id">
      <generator class="sequence" />
    </id>
    <property name="message" />
  </class>
</hibernate-mapping>

И следующие юнит-тесты:

@Test
public void testFieldBean() {
    Session session = sessionFactory.openSession();
    Transaction tx = session.beginTransaction();
    FieldBean fb = new FieldBean("field");
    Long id = (Long) session.save(fb);
    tx.commit();
    session.close();

    session = sessionFactory.openSession();
    tx = session.beginTransaction();
    fb = (FieldBean) session.load(FieldBean.class, id);
    System.out.println(fb.getId());
    tx.commit();
    session.close();
}

@Test
public void testPropBean() {
    Session session = sessionFactory.openSession();
    Transaction tx = session.beginTransaction();
    PropBean pb = new PropBean("prop");
    Long id = (Long) session.save(pb);
    tx.commit();
    session.close();

    session = sessionFactory.openSession();
    tx = session.beginTransaction();
    pb = (PropBean) session.load(PropBean.class, id);
    System.out.println(pb.getId());
    tx.commit();
    session.close();
}

Вы увидите тонкую разницу в необходимом выборе:

Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    insert 
    into
        FIELD_BEAN
        (message, id) 
    values
        (?, ?)
Hibernate: 
    select
        fieldbean0_.id as id1_0_,
        fieldbean0_.message as message1_0_ 
    from
        FIELD_BEAN fieldbean0_ 
    where
        fieldbean0_.id=?
0
Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    insert 
    into
        PROP_BEAN
        (message, id) 
    values
        (?, ?)
1

То есть, для вызова fb.getId() требуется выбор, тогда как pb.getId() - нет.

...