Ответ 1
Создание отношений с аннотациями
Предположим, что все классы, аннотированные с помощью @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;
}
Uni-Directional One to Many Отношения с помощью таблицы соединений, управляемой пользователем
public class Foo{
private UUID fooId;
@OneToMany
@JoinTable(name="FOO_BAR",
joinColumns = @JoinColumn(name="fooId"),
inverseJoinColumns = @JoinColumn(name="barId"))
private List<Bar> 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 Безопасность при настройке объекта User
, у которого есть список Role
, который они могут выполнять. Вы можете добавлять и удалять роли для пользователя, не беспокоясь о том, что каскады удаляют Role
.
Двунаправленное отношение "один ко многим" с использованием сопоставления внешних ключей
public class Foo{
private UUID fooId;
@OneToMany(mappedBy = "bar")
private List<Bar> 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<Bar> bars;
}
public class Bar{
private UUID barId;
@OneToMany
@JoinTable(name="FOO_BAR",
joinColumns = @JoinColumn(name="barId"),
inverseJoinColumns = @JoinColumn(name="fooId"))
private List<Foo> foos;
}
Двунаправленные Многие для многих, используя пользовательский элемент таблицы соединений
Обычно используется, когда вы хотите хранить дополнительную информацию о объекте объединения, например дату создания отношения.
public class Foo{
private UUID fooId;
@OneToMany(mappedBy = "bar")
private List<FooBar> bars;
}
public class Bar{
private UUID barId;
@OneToMany(mappedBy = "foo")
private List<FooBar> 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
имеет список Roles
, объявленный на нем. В большинстве приложений система будет обрабатывать экземпляры объекта User
чаще, чем экземпляры объекта Roles
. Следовательно, я бы сделал объект Role
принадлежащей стороне отношения и манипулировал объектами Role
через список Role
на User
каскадом. Для практического примера см. Пример двунаправленного от одного до большого. Как правило, вы будете каскадировать все изменения в этом сценарии, если у вас нет конкретных требований делать иначе.
Определение типа fetchType
Лентабельные коллекции привели к большему количеству проблем с SO, чем мне хотелось бы посмотреть, потому что по умолчанию Hibernate будет загружать связанные объекты лениво. Не имеет значения, является ли отношение одним-к-одному или многими-ко-многим в соответствии с документами Hibernate:
По умолчанию Hibernate использует ленивый выбор для наборов и ленивый выбор прокси для однозначных ассоциаций. Эти значения по умолчанию имеют смысл для большинства ассоциаций в большинстве приложений.
Рассмотрим эти два цента о том, когда следует использовать fetchType.LAZY
vs fetchType.EAGER
на ваших объектах. Если вы знаете, что в 50% случаев вам не потребуется доступ к коллекции на родительском объекте, я бы использовал fetchType.LAZY
.
Преимущества производительности в этом огромны и растут только по мере того, как вы добавляете больше объектов в свою коллекцию. Это связано с тем, что для загруженной коллекции Hibernate выполняет тонну проверки за кулисами, чтобы гарантировать, что ни одна из ваших данных не устарела. Хотя я рекомендую использовать Hibernate для коллекций, имейте в виду, что для fetchType.EAGER
существует ограничение производительности **. Однако возьмите наш пример объекта Person
. Довольно вероятно, что при загрузке Person
мы захотим узнать, что они выполняют Roles
. Обычно я отмечу эту коллекцию как fetchType.EAGER
. НЕ РЕФЛЕКСИВНО ОТМЕТЬТЕ СВОЕ СОБРАНИЕ КАК fetchType.EAGER
ПРОСТО ПОЛУЧИТЬ ВОКРУГ A 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
по соображениям производительности. Связанный ответ более подробно рассматривается, если вы действительно заинтересованы.
Если вы хотите прочитать меня, как я повторяю, не стесняйтесь проверить мой другой ответ здесь
Определение каскадного направления
Спящий режим может выполнять каскадные операции либо в обоих направлениях, либо в двух направлениях. Поэтому, если у вас есть список Role
на User
, вы можете каскадировать изменения в Role
в обоих направлениях. Если вы измените имя конкретного Role
на User
, спящий режим может автоматически обновить связанный Role
в таблице Role
.
Однако это не всегда желаемое поведение. Если вы думаете об этом, в этом случае внесение изменений в Role
на основе изменений в User
не имеет никакого смысла. Однако имеет смысл идти в противоположном направлении. Измените имя Role
на самом объекте Role
, и это изменение может быть каскадным для всех объектов User
, которые имеют на нем Role
.
С точки зрения эффективности имеет смысл создавать/обновлять объекты Role
, сохраняя объект User
, к которому они принадлежат. Это означает, что вы отмечаете аннотацию @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<Roles> 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
, установленный в анноте @OneToMany
, Hibernate удалит текущий объект "осиротевший" из базы данных каскадом.
Удаление сирот не должно быть включено в каждом случае. Фактически, наличие orphanRemoval в нашем примере выше не имеет смысла. Просто потому, что no User
может выполнять любое действие, которое представляет объект ROLE_EXAMPLE, это не означает, что любое будущее User
никогда не сможет выполнить действие.
Этот Q & A предназначен для дополнения официальной документации Hibernate, которая имеет большую конфигурацию XML для этих отношений.
Эти примеры не предназначены для копирования в производственный код. Они являются типичными примерами создания и управления различными объектами и их отношениями с помощью JPA-аннотаций для настройки Hibernate 4 в рамках Spring Framework. В примерах предполагается, что все классы имеют поля ID, объявленные в следующем формате: fooId
. Тип этого поля идентификатора не имеет значения.
** Недавно нам пришлось отказаться от использования Hibernate для задания на вставку, где мы вставляли в базу данных через 80 000+ объектов через коллекцию. Hibernate съел всю память кучи, проверив сборку и разбив систему.
<суб > ПРЕДУПРЕЖДЕНИЕ: Я НЕ ЗНАЮ, ЕСЛИ ЭТИ ПРИМЕРЫ БУДУТ РАБОТАТЬ С СТАНДАРТНЫМ ЛИНИЕМ
Я никоим образом не связан с командой Hibernate или Hibernate. Я предоставляю эти примеры, поэтому у меня есть ссылка на то, когда я отвечаю на вопросы по тегу Hibernate. Эти примеры и дискуссии основаны на моем собственном мнении, а также на том, как я разрабатываю свои приложения с помощью Hibernate. Эти примеры никоим образом не являются исчерпывающими. Я основываю их на общих ситуациях, в которых я использовал Hibernate в прошлом.
Если у вас возникли проблемы с попыткой реализовать эти примеры, не комментируйте и не ожидайте, что я исправлю вашу проблему. Часть обучения Hibernate изучает вход и выход его API. Если в примерах есть ошибка, не стесняйтесь их редактировать.