如何在Hibernate 4和Spring中使用注释来定义不同types的关系?

我有两个class级, FooBar ,如下所示:

 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对象成为关系的拥有者,并通过级联的RoleUser列表中操作Role对象。 有关实际示例,请参阅双向One to Many示例。 通常情况下,您将级联在此scheme中的所有更改,除非您有特殊的要求,否则。

确定你的fetchType

由于默认情况下,Hibernate会延迟加载相关对象,所以懒于提取集合导致了更多的问题。 根据Hibernate文档,关系是一对一还是多对多并不重要:

默认情况下,Hibernate使用集合的懒惰select读取和单值关联的lazy proxy fetching。 这些默认值对大多数应用程序中的大多数关联都有意义。

考虑这个我的两个fetchType.LAZYfetchType.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是listOfRolesRole的数量,但是随着Hibernate将每个RoleUser表中,还产生了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的内部和外部。 如果这些例子出现错误,请随时编辑它们。