使用批注创建关系
假设所有类都带有@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<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 Security一起使用。您可以向用户添加和删除角色,而不必担心级联删除 。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;
}
使用休眠托管联接表的双向多对多
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
确定您的抓取类型
懒惰地获取集合导致SO上的问题比我想要看到的要多,因为默认情况下Hibernate会懒惰地加载相关对象。根据Hibernate文档,这种关系是一对一还是多对多并不重要:
默认情况下,Hibernate 对集合使用惰性选择提取,对单值关联使用惰性代理提取。这些默认值对于大多数应用程序中的大多数关联都有意义。
考虑一下,我的两分钱何时使用与你的对象。如果您知道50%的时间不需要访问父对象上的集合,我将使用.fetchType.LAZY
fetchType.EAGER
fetchType.LAZY
性能优势是巨大的,并且只会随着向集合中添加更多对象而增长。这是因为对于热切加载的集合,Hibernate会进行大量的幕后检查,以确保您的任何数据都不会过期。虽然我确实提倡对集合使用 Hibernate,但请注意,使用 会对性能造成影响**。但是,以我们的对象为例。当我们加载时,我们很可能想知道它们的性能。我通常会将此集合标记为 .不要条件反射地将你的集合标记为 fetchType.EAGER
只是为了绕过一个 LazyInitializationException
。这不仅是出于性能原因造成的不良影响,而且通常表明您存在设计问题。问问自己,这个集合是否真的是一个热切加载的集合,或者我这样做只是为了在这个方法中访问集合。Hibernate有办法解决这个问题,这不会对你的运营性能产生太大的影响。如果要仅为这一次调用初始化延迟加载的集合,则可以在层中使用以下代码。fetchType.EAGER
Person
Person
Roles
fetchType.EAGER
Service
//Service Class
@Override
@Transactional
public Person getPersonWithRoles(UUID personId){
Person person = personDAO.find(personId);
Hibernate.initialize(person.getRoles());
return person;
}
强制创建和加载集合对象的调用。但是,请注意,如果您仅将其传递到实例,您将获得背面的代理。有关详细信息,请参阅文档。此方法的唯一缺点是,您无法控制Hibernate如何实际获取对象集合。如果你想控制这一点,那么你可以在你的DAO中这样做。Hibernate.initialize
Person
Person
//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 将为对象生成一个查询,然后在插入到数据库中后级联创建 。然后,这些插入语句将能够使用 的 PK 作为其外键,因此您最终会得到 N + 1 个插入语句,其中 N 是用户列表中的对象数。INSERT
User
Role
User
User
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 是 中的数目,但当休眠将每个语句级联到表中时,也会生成 N 个更新语句。与 O(1) 相比,此 DAO 方法具有比我们以前的方法 O(n) 更高的时间复杂度,因为您必须遍历角色列表。如果可能的话,避免这样做。Role
listOfRoles
Role
User
然而,在实践中,通常关系的拥有方将是你标记级联的地方,你通常会级联所有的东西。
孤儿删除
如果您删除与对象的所有关联,则休眠可以为您解决。假设您有一个谁有一个列表,并且在此列表中有指向5个不同角色的链接。假设您删除了一个被调用ROLE_EXAMPLE并且碰巧ROLE_EXAMPLE不存在于任何其他对象上。如果已对注释进行设置,Hibernate 将通过级联从数据库中删除现在“孤立”的 Role 对象。User
Role
Role
User
orphanRemoval = true
@OneToMany
不应在每种情况下都启用孤立项删除。事实上,在上面的示例中使用 orphanRemoval 是没有意义的。仅仅因为 no 可以执行ROLE_EXAMPLE对象所代表的任何操作,这并不意味着任何未来都永远无法执行该操作。User
User
本问答旨在补充官方Hibernate文档,该文档为这些关系提供了大量的XML配置。
这些示例不应复制粘贴到生产代码中。它们是如何使用JPA注释在Spring Framework中配置Hibernate 4来创建和管理各种对象及其关系的通用示例。这些示例假定所有类都具有以下格式声明的 ID 字段:。此 ID 字段的类型不相关。fooId
** 我们最近不得不放弃使用Hibernate进行插入作业,通过集合将<80,000多个对象插入到数据库中。休眠吞噬了所有对集合进行检查的堆内存,并使系统崩溃。
免责声明:我不知道这些示例是否适用于独立休眠
我与Hibernate或Hibernate开发团队没有任何关系。我提供了这些示例,因此当我回答Hibernate标签上的问题时,我有一个参考。这些示例和讨论是基于我自己的观点以及我如何使用Hibernate开发我的应用程序。这些例子并不全面。我基于我过去使用Hibernate的常见情况。
如果您在尝试实现这些示例时遇到问题,请不要发表评论,并希望我解决您的问题。学习Hibernate的一部分是学习其API的输入和输出。如果示例有误,请随时编辑它们。