Как использовать annotations для определения разных типов отношений в Hibernate 4 и Spring?

У меня есть два classа: Foo и Bar :

 public class Foo { private Long fooId; private Bar bar; //Yes, this doesn't actually make any sense, //having both a list and a single object here, its an example. private List bars; } public class Bar { private Long barId; private Foo foo; } 

Как реализовать (однонаправленные / двунаправленные) отношения «один ко многим», «многие-к-одному» или «многие ко многим», используя annotations для Hibernate 4 для этих classов?

Также как мне настроить мой один-ко-многим для удаления сирот, ленивую загрузку и что вызывает LazyInitialiaizationException при работе с коллекциями и как решить проблему?

Создание отношений с аннотациями

Предположим, что все classы, аннотированные с @Entity и @Table

Однонаправленные отношения «один к одному»

 public class Foo{ private UUID fooId; @OneToOne private Bar bar; } public class Bar{ private UUID barId; //No corresponding mapping to Foo.class } 

Двунаправленные отношения «один к одному», управляемые Foo.class

 public class Foo{ private UUID fooId; @OneToOne(cascade = CascadeType.ALL) @JoinColumn(name = "barId") private Bar bar; } public class Bar{ private UUID barId; @OneToOne(mappedBy = "bar") private Foo foo; } 

Однонаправленные отношения «один ко многим» с использованием таблицы соединений с управляемым пользователем

 public class Foo{ private UUID fooId; @OneToMany @JoinTable(name="FOO_BAR", joinColumns = @JoinColumn(name="fooId"), inverseJoinColumns = @JoinColumn(name="barId")) private List bars; } public class Bar{ private UUID barId; //No Mapping specified here. } @Entity @Table(name="FOO_BAR") public class FooBar{ private UUID fooBarId; @ManyToOne @JoinColumn(name = "fooId") private Foo foo; @ManyToOne @JoinColumn(name = "barId") private Bar bar; //You can store other objects/fields on this table here. } 

Очень часто используется с Spring Security при настройке объекта User которого есть список Role , который они могут выполнять. Вы можете добавлять и удалять роли для пользователя, не беспокоясь о том, что каскады удаляют Role .

Двунаправленные отношения «один ко многим» с использованием сопоставления внешних ключей

 public class Foo{ private UUID fooId; @OneToMany(mappedBy = "bar") private List bars; } public class Bar{ private UUID barId; @ManyToOne @JoinColumn(name = "fooId") private Foo foo; } 

Двунаправленное много для многих, используя таблицу управления соединением Hibernate

 public class Foo{ private UUID fooId; @OneToMany @JoinTable(name="FOO_BAR", joinColumns = @JoinColumn(name="fooId"), inverseJoinColumns = @JoinColumn(name="barId")) private List bars; } public class Bar{ private UUID barId; @OneToMany @JoinTable(name="FOO_BAR", joinColumns = @JoinColumn(name="barId"), inverseJoinColumns = @JoinColumn(name="fooId")) private List foos; } 

Двунаправленные от многих до многих, используя пользовательский интерфейс.

Обычно используется, когда вы хотите хранить дополнительную информацию об объекте объединения, например дату создания отношения.

 public class Foo{ private UUID fooId; @OneToMany(mappedBy = "bar") private List bars; } public class Bar{ private UUID barId; @OneToMany(mappedBy = "foo") private List foos; } @Entity @Table(name="FOO_BAR") public class FooBar{ private UUID fooBarId; @ManyToOne @JoinColumn(name = "fooId") private Foo foo; @ManyToOne @JoinColumn(name = "barId") private Bar bar; //You can store other objects/fields on this table here. } 

Определение того, какая сторона двунаправленного отношения «владеет» отношением:

Это один из самых сложных аспектов разработки отношений Hibernate, потому что Hibernate будет работать правильно независимо от способа установления отношений. Единственное, что изменится, – это таблица, в которой хранится внешний ключ. Как правило, объект, который у вас есть, будет иметь отношения.

Пример. User объект имеет список объявленных Ролей. В большинстве приложений система будет обрабатывать экземпляры объекта User чаще, чем экземпляры объекта Roles . Следовательно, я бы сделал объект Role ответственной стороной отношений и манипулировал объектами Role через список Role на User помощью каскада. Для практического примера см. Двунаправленный пример от одного до многих . Как правило, вы будете каскадировать все изменения в этом сценарии, если у вас нет конкретного требования сделать иначе.

Определение вашего типа fetchType

Лентабельные коллекции привели к большему количеству проблем на SO, чем мне хотелось бы посмотреть, потому что по умолчанию Hibernate будет загружать связанные объекты лениво. Не имеет значения, является ли отношение одним-к-одному или многими-ко-многим в соответствии с документами Hibernate:

По умолчанию Hibernate использует ленивый выбор для наборов и ленивый выбор прокси для однозначных ассоциаций. Эти значения по умолчанию имеют смысл для большинства ассоциаций в большинстве приложений.

Рассмотрим эти два цента о том, когда использовать fetchType.LAZY vs fetchType.EAGER для ваших объектов. Если вы знаете, что в 50% случаев вам не понадобится доступ к коллекции на родительском объекте, я бы использовал fetchType.LAZY .

Преимущества этой работы огромны и растут только по мере того, как вы добавляете больше объектов в свою коллекцию. Это связано с тем, что для загруженной коллекции Hibernate выполняет тонну проверки за кулисами, чтобы гарантировать, что ни одна из ваших данных не устарела. Хотя я рекомендую использовать Hibernate для коллекций, имейте в fetchType.EAGER для использования fetchType.EAGER существует штраф производительности ** . Однако возьмите пример объекта Person . Довольно вероятно, что когда мы загрузим Person мы захотим узнать, какие Roles они выполняют. Обычно я fetchType.EAGER эту коллекцию как fetchType.EAGER . НЕ fetchType.EAGER СВОЕ СОБРАНИЕ, КАК fetchType.EAGER ПРОСТО, ЧТОБЫ ПОЛУЧИТЬ LazyInitializationException . Не только это плохо по соображениям производительности, это обычно указывает на то, что у вас проблема с дизайном. Спросите себя, должна ли эта коллекция действительно быть загруженной коллекцией, или я делаю это только для доступа к коллекции в этом методе. У Hibernate есть способы обойти это, что не сильно влияет на производительность ваших операций. Вы можете использовать следующий код в своем Service если вы хотите инициализировать лениво загруженную коллекцию только для этого вызова.

 //Service Class @Override @Transactional public Person getPersonWithRoles(UUID personId){ Person person = personDAO.find(personId); Hibernate.initialize(person.getRoles()); return person; } 

Вызов Hibernate.initialize заставляет создавать и загружать объект коллекции. Однако будьте осторожны, если вы передадите только экземпляр Person , вы получите прокси своего Person . Дополнительную информацию см. В документации . Единственным недостатком этого метода является то, что у вас нет контроля над тем, как Hibernate действительно будет получать вашу коллекцию объектов. Если вы хотите контролировать это, вы можете сделать это в своем DAO.

 //DAO @Override public Person findPersonWithRoles(UUID personId){ Criteria criteria = sessionFactory.getCurrentSession().createCritiera(Person.class); criteria.add(Restrictions.idEq(personId); criteria.setFetchMode("roles", FetchMode.SUBSELECT); } 

Производительность здесь зависит от того, что вы указали FetchMode . Я прочитал ответы, которые говорят, чтобы использовать FetchMode.SUBSELECT по соображениям производительности. Связанный ответ более подробно рассматривается, если вы действительно заинтересованы.

Если вы хотите прочитать меня, как я повторяю, не стесняйтесь проверить мой другой ответ здесь

Определение каскадного направления

Hibernate может выполнять каскадные операции либо в обоих направлениях, либо в двух направлениях. Поэтому, если у вас есть список Role User вы можете каскадировать изменения в Role в обоих направлениях. Если вы измените имя определенной Role в спящем режиме, User может автоматически обновить связанную Role в таблице Role .

Однако это не всегда желаемое поведение. Если вы думаете об этом, в этом случае внесение изменений в Role на основе изменений в User не имеет никакого смысла. Однако имеет смысл идти в противоположном направлении. Измените имя Role на самом объекте Role , и это изменение может быть каскадировано для всех объектов User которые имеют эту Role на нем.

С точки зрения эффективности, имеет смысл создавать / обновлять объекты Role , сохраняя объект User которому они принадлежат. Это означает, что вы @OneToMany аннотацию @OneToMany как @OneToMany . Приведу пример:

 public User saveOrUpdate(User user){ getCurrentSession.saveOrUpdate(user); return user; } 

В приведенном выше примере Hibernate будет генерировать запрос INSERT для объекта User и затем каскадировать создание Role после того, как User был вставлен в базу данных. Эти вставные операторы смогут использовать PK User качестве своего внешнего ключа, поэтому в итоге вы получите инструкции для вставки N + 1, где N – количество объектов Role в списке пользователей.

И наоборот, если вы хотите сохранить отдельные объекты Role каскадирующие обратно к объекту User , могут быть выполнены:

 //Assume that user has no roles in the list, but has been saved to the //database at a cost of 1 insert. public void saveOrUpdateRoles(User user, List listOfRoles){ for(Role role : listOfRoles){ role.setUser(user); getCurrentSession.saveOrUpdate(role); } } 

Это приводит к вставкам N + 1, где N – число Role в listOfRoles , но также N операторов обновления, которые генерируются, поскольку Hibernate каскадирует добавление каждой Role в таблицу User . Этот метод DAO имеет более высокую временную сложность, чем наш предыдущий метод O (n), в отличие от O (1), потому что вам нужно перебирать список ролей. Избегайте этого, если это вообще возможно.

На практике, однако, обычно собственная сторона отношений будет там, где вы отмечаете свои каскады, и вы обычно будете каскадировать все.

Удаление сирот

Hibernate может работать для вас, если вы удалите все ассоциации с объектом. Предположим, у вас есть User которого есть список Role , и в этом списке есть ссылки на 5 разных ролей. Допустим, вы удалили Role называемую ROLE_EXAMPLE, и случается, что ROLE_EXAMPLE не существует ни на одном другом объекте User . Если у вас есть orphanRemoval = true установленный в annotations @OneToMany , Hibernate удалит теперь «осиротевший» объект Role из базы данных каскадом.

В любом случае удаление сирот не должно быть включено. Фактически, наличие orphanRemoval в нашем примере выше не имеет смысла. Просто потому, что ни один User может выполнять любое действие, которое представляет объект ROLE_EXAMPLE, это не означает, что любой будущий User никогда не сможет выполнить действие.

Этот Q & A предназначен для дополнения официальной документации Hibernate, которая имеет большую конфигурацию XML для этих отношений.

Эти примеры не предназначены для копирования в производственный код. Они являются типичными примерами создания и управления различными объектами и их отношениями с использованием аннотаций JPA для настройки Hibernate 4 в Spring Framework. В примерах предполагается, что все classы имеют поля ID, объявленные в следующем формате: fooId . Тип этого поля идентификатора не имеет значения.

** Нам недавно пришлось отказаться от использования Hibernate для задания на вставку, где мы вставляли <80 000+ объектов в базу данных через коллекцию. Hibernate съел всю память кучи, проверив сборку и разбив систему.

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Я НЕ ЗНАЮ, ЕСЛИ ЭТИ ПРИМЕРЫ БУДУТ РАБОТАТЬ С STANDALONE HIBERNATE

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

Если вы сталкиваетесь с проблемами, пытающимися реализовать эти примеры, не комментируйте и не ожидайте, что я исправлю вашу проблему. Часть обучения Hibernate изучает и использует API. Если в примерах есть ошибка, не стесняйтесь их редактировать.

  • Hibernate: MyInterceptor # onFlushDirty никогда не вызывается
  • Spring 3.0 делает ответ JSON с помощью конвертера сообщений Jackson
  • Spring MVC - Как вернуть простую строку в JSON в Rest Controller
  • Как я могу отключить пулы исполнителей / планировщиков Spring Spring до того, как все остальные бобы в веб-приложении будут уничтожены?
  • Настраивать DataSource программно в Spring Boot
  • Загрузка файла с весенних controllerов
  • Спящий режим, весна, JPS и изоляция - индивидуальная изоляция не поддерживается
  • Spring настроить формат @ResponseBody JSON
  • Как вызвать метод после инициализации бина?
  • Почему Spring's ApplicationContext.getBean считается плохим?
  • Использование запроса Hibernate: двоеточие обрабатывается как параметр / экранирование двоеточия
  • Давайте будем гением компьютера.