[英]How do I use annotations to define different types of relationships in Hibernate 4 and Spring?
我有两个类, 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<Bar> bars;
}
public class Bar {
private Long barId;
private Foo foo;
}
如何使用Hibernate 4的注释为这些类实现(单向/双向)一对多,多对一或多对多关系?
另外,如何配置我的一对多用于孤儿删除,延迟加载以及在处理集合时导致LazyInitialiaizationException
原因以及如何解决问题?
假设所有类都注释了@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.
}
在设置具有可以执行的Role
列表的User
对象时,通常与Spring Security一起使用。 您可以向用户添加和删除角色,而无需担心级联删除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
列表。 在大多数应用程序中,系统将比Roles
对象的实例更频繁地操作User
对象的实例。 因此,我会使Role
对象之间的关系的拥有方和操纵Role
通过的对象列表Role
的一个User
通过级联。 有关实际示例,请参阅双向一对多示例。 通常,您将在此方案中级联所有更改,除非您有特定要求。
由于默认情况下Hibernate将懒惰地加载相关对象,因此Lazily获取的集合导致了更多关于SO的问题。 根据Hibernate文档,这种关系是一对一还是多对多并不重要:
默认情况下,Hibernate对集合使用延迟选择提取,对单值关联使用延迟代理提取。 这些默认值对大多数应用程序中的大多数关联都有意义。
考虑一下我在你的对象上使用fetchType.LAZY
和fetchType.EAGER
两分钱。 如果您知道50%的时间不需要访问父对象上的集合,那么我将使用fetchType.LAZY
。
性能优势是巨大的,只有在您向集合中添加更多对象时才会增长。 这是因为对于急切加载的集合,Hibernate会进行大量的幕后检查,以确保您的数据都不会过时。 虽然我提倡使用Hibernate进行集合,但请注意使用fetchType.EAGER
会有性能损失** 。 但是,请参阅我们的Person
对象示例。 当我们加载一个Person
时,我们很可能想知道他们执行的Roles
。 我通常会将此集合标记为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可以在双向关系中以一种或两种方式级联操作。 因此,如果您在User
上有Role
列表,则可以在两个方向上级联对Role
的更改。 如果更改User
上特定Role
的名称,Hibernate可以自动更新Role
表上的关联Role
。
然而,这并不总是期望的行为。 如果您考虑一下,在这种情况下,根据对User
更改对Role
进行更改没有任何意义。 然而,相反的方向是有意义的。 在Role
对象本身上更改Role
的名称,并且该更改可以级联到其上具有该Role
所有User
对象。
在效率方面,通过保存属于它们的User
对象来创建/更新Role
对象是有意义的。 这意味着您将@OneToMany
注释标记为级联注释。 我举个例子:
public User saveOrUpdate(User user){
getCurrentSession.saveOrUpdate(user);
return user;
}
在上面的示例中,Hibernate将为User
对象生成INSERT
查询,然后在将User
插入数据库后级联创建Role
。 然后,这些insert语句将能够使用User
的PK作为其外键,因此您最终会得到N + 1个insert语句,其中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是listOfRoles
中Role
的数量,但是当Hibernate级联将每个Role
到User
表时,也会生成N个更新语句。 这个DAO方法比我们之前的方法O(n)具有更高的时间复杂度,而不是O(1),因为你必须遍历角色列表。 尽可能避免这种情况。
然而,在实践中,通常关系的拥有方将是你标记你的级联,你通常会级联一切。
如果删除对象的所有关联,Hibernate可以为您解决问题。 假设您有一个具有Role
列表的User
,并且在此列表中是指向5个不同角色的链接。 假设您删除了一个名为ROLE_EXAMPLE的Role
,并且碰巧ROLE_EXAMPLE在任何其他User
对象上都不存在。 如果在@OneToMany
批注上设置了orphanRemoval = true
,Hibernate将通过级联从数据库中删除现在的'孤立'Role对象。
不应在每种情况下都启用孤立删除。 实际上,在上面的示例中使用orphanRemoval是没有意义的。 仅仅因为没有User
可以执行ROLE_EXAMPLE对象所代表的任何操作,这并不意味着任何将来的User
将永远无法执行该操作。
此Q&A旨在补充官方Hibernate文档,该文档为这些关系提供了大量XML配置。
这些示例无意复制粘贴到生产代码中。 它们是如何使用JPA注释在Spring Framework中配置Hibernate 4来创建和管理各种对象及其关系的通用示例。 这些示例假定所有类都具有以下列格式声明的ID字段: fooId
。 此ID字段的类型不相关。
**我们最近不得不放弃使用Hibernate进行插入作业,我们通过集合将<80,000+个对象插入到数据库中。 Hibernate在检查集合时崩溃了所有堆内存,并使系统崩溃。
免责声明:我不知道这些示例是否与STANDALONE HIBERNATE合作
我与Hibernate或Hibernate开发团队没有任何关系。 我提供这些示例,所以我有一个参考指向何时我正在回答有关Hibernate标记的问题。 这些示例和讨论是基于我自己的观点以及如何使用Hibernate开发应用程序。 这些例子绝不是全面的。 我基于我过去使用Hibernate的常见情况。
如果您在尝试实施这些示例时遇到问题,请不要发表评论并希望我解决您的问题。 学习Hibernate的一部分就是学习它的API。 如果示例中存在错误,请随时编辑它们。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.