如何在Hibernate 4和Spring中使用注释来定义不同types的关系?
我有两个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<Bar> bars; } public class Bar { private Long barId; private Foo foo; }
如何使用这些类的Hibernate 4注释实现(单向/双向)一对多,多对一或多对多的关系?
另外,如何configuration我的一对多的孤儿删除,延迟加载和LazyInitialiaizationException
处理集合时如何解决问题?
与注释创build关系
假设所有使用@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.classpipe理的双向一对一关系
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; }
单向一对多关系使用用户pipe理的连接表
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一起使用。 您可以添加和删除angular色,而不必担心级联删除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; }
双向多对多使用Hibernatepipe理的连接表
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; }
双向多对多使用用户pipe理的连接表对象
当您想要在连接对象上存储额外信息(如创build关系的date)时常用。
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关系的棘手方面之一,因为无论build立关系的方式,Hibernate都能正常运行。 唯一会改变的是外键存储在哪个表中。 一般来说,你有一个集合的对象将拥有这种关系。
示例: User
对象具有在其上声明的Roles
列表。 在大多数应用程序中,系统将比Roles
对象的实例更频繁地操作User
对象的实例。 因此,我将使Role
对象成为关系的拥有者,并通过级联的Role
在User
列表中操作Role
对象。 有关实际示例,请参阅双向One to Many示例。 通常情况下,您将级联在此scheme中的所有更改,除非您有特殊的要求,否则。
确定你的fetchType
由于默认情况下,Hibernate会延迟加载相关对象,所以懒于提取集合导致了更多的问题。 根据Hibernate文档,关系是一对一还是多对多并不重要:
默认情况下,Hibernate使用集合的懒惰select读取和单值关联的lazy proxy fetching。 这些默认值对大多数应用程序中的大多数关联都有意义。
考虑这个我的两个fetchType.LAZY
, fetchType.EAGER
在你的对象上使用fetchType.LAZY
vs fetchType.EAGER
。 如果你知道50%的时间你不需要访问你的父对象的集合,我会使用fetchType.LAZY
。
性能优势是非常巨大的,只有在向对象中添加更多对象时才会有所增长。 这是因为对于一个急切的加载的集合,Hibernate做了大量的幕后检查,以确保你的数据没有过期。 虽然我主张使用Hibernate进行集合,但请注意,使用fetchType.EAGER
性能。 但是,以我们的Person
对象为例。 当我们加载一个Person
我们很想知道他们的Roles
是什么。 我通常将这个集合标记为fetchType.EAGER
。 请不要将您的collections作为fetchType.EAGER标记fetchType.EAGER
简单地获取LazyInitializationException
。 不仅是因为性能原因,它通常表明你有一个devise问题。 问问你自己,如果这个集合实际上是一个急于加载的集合,或者我这样做只是为了访问这个方法的集合。 Hibernate有办法解决这个问题,这不会影响你的操作性能。 如果你想为这一个调用初始化一个延迟加载的集合,你可以在你的Service
层使用下面的代码。
//Service Class @Override @Transactional public Person getPersonWithRoles(UUID personId){ Person person = personDAO.find(personId); Hibernate.initialize(person.getRoles()); return person; }
对Hibernate.initialize
的调用强制创build和加载集合对象。 但是,要小心,如果你只传递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
对象来创build/更新Role
对象是有意义的。 这意味着你将@OneToMany
注释标记为级联标注。 我举个例子:
public User saveOrUpdate(User user){ getCurrentSession.saveOrUpdate(user); return user; }
在上面的例子中,Hibernate将为User
对象生成一个INSERT
查询,然后在User
被插入到数据库后级联Role
的创build。 这些插入语句将能够使用User
的PK作为它们的外键,所以你最终将得到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是listOfRoles
中Role
的数量,但是随着Hibernate将每个Role
到User
表中,还产生了N个更新语句。 这个DAO方法的时间复杂度比我们以前的方法O(n)要高,而不是O(1),因为你必须迭代angular色列表。 尽可能避免这种情况。
然而,在实践中,通常情况下,关系的拥有方将会是你标记级联的地方,而且你通常会把所有的一切级联起来。
孤儿去除
如果你删除所有关联到一个对象,Hibernate可以为你工作。 假设你有一个拥有Role
列表的User
,并且在这个列表中有5个不同angular色的链接。 比方说,你删除了一个名为ROLE_EXAMPLE的Role
,并且碰巧ROLE_EXAMPLE在任何其他User
对象上都不存在。 如果你在@OneToMany
注解中设置了orphanRemoval = true
,Hibernate将会通过级联从数据库中删除现在的'孤立的'angular色对象。
孤儿删除不应该在每一个情况下启用。 事实上,在我们上面的例子中使用orphanRemoval是没有意义的。 仅仅因为没有User
可以执行ROLE_EXAMPLE对象所代表的任何操作,这并不意味着将来的User
将永远无法执行该操作。
这个问答旨在补充正式的Hibernate文档,对这些关系有大量的XMLconfiguration。
这些示例并不意味着被复制粘贴到生产代码中。 它们是如何创build和pipe理各种对象及其关系的通用示例,它们使用JPA批注在Spring框架中configurationHibernate 4。 这些示例假定所有类都有以下格式声明的ID字段: fooId
。 这个ID字段的types是不相关的。
**我们最近不得不放弃使用Hibernate来插入作业,我们通过一个集合将<80,000个以上的对象插入到数据库中。 Hibernate吃了所有的堆内存检查收集和崩溃的系统。
免责声明:我不知道这些例子是否适用于独立的湖泊
我不以任何方式与Hibernate或Hibernate开发团队有联系。 我提供了这些示例,所以我有一个参考指向何时在Hibernate标记上回答问题。 这些例子和讨论是基于我自己的观点,以及如何使用Hibernate开发我的应用程序。 这些例子并不全面。 我基于他们过去使用Hibernate的常见情况。
如果您在尝试实施这些示例时遇到问题,请不要发表评论,并期望我解决您的问题。 学习的一部分Hibernate正在学习API的内部和外部。 如果这些例子出现错误,请随时编辑它们。