我有两个课程,Foo和Bar,如下所示:
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在处理集合时会导致a的原因以及如何解决问题?
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. }
当设置一个User对象时,Spring Security经常与Spring Security一起使用,这些对象Role可以执行的列表。您可以向用户添加和删除角色,而不必担心级联删除Role。
Spring Security
使用外键映射的双向一对多关系
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托管联接表的双向多对多
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通过by级联Role上的列表处理对象User。有关实际示例,请参见双向“一对多”示例。通常,除非有特殊要求,否则您将级联此方案中的所有更改。
确定您的fetchType
延迟获取的集合导致SO上的问题比我关心的要多,因为默认情况下,Hibernate会延迟加载相关对象。根据Hibernate文档,关系是一对一还是多对多并不重要:
默认情况下,Hibernate对集合使用延迟选择获取,对单值关联使用延迟代理获取。对于大多数应用程序中的大多数关联而言,这些默认设置有意义。
考虑一下这是我何时使用fetchType.LAZYvs fetchType.EAGER在对象上的两分钱。如果您知道50%的时间不需要访问父对象上的集合,则可以使用fetchType.LAZY。
fetchType.LAZYvs fetchType.EAGER
这样的性能优势是巨大的,并且只有在您向集合中添加更多对象时,它才会增长。这是因为对于急切加载的集合,Hibernate在后台进行了大量检查以确保您的数据都没有过期。虽然我确实主张将Hibernate用于集合,但是请注意,使用会降低性能**fetchType.EAGER。但是,以我们的Person对象为例。当我们加载a时,我们很可能Person想知道Roles它们的性能。我通常将此收藏标记为fetchType.EAGER。不要自反地将您的收藏标记为fetchType.EAGER简单的周围LazyInitializationException。它不仅由于性能原因而不好,而且通常表明您遇到了设计问题。问问自己,这个集合实际上是一个急切加载的集合,还是我只是通过这种方法访问该集合?Hibernate有解决此问题的方法,不会对您的操作性能产生太大影响。Service如果只想为一次调用初始化延迟加载的集合,则可以在图层中使用以下代码。
**fetchType.EAGE
fetchType.EAGER
LazyInitializationException
//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可以双向关系中的一种或两种方式级联操作。所以,如果你有一个列表Role的一个User可以级联变化Role的两个方向。如果您Role在UserHibernate 上更改了特定的名称,则可以自动更新Table 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用户列表中对象的数量。
INSERT
User
PK User
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,但作为hibernate级联每增加一个的产生也可为N更新语句Role的User表。与您以前的方法O(n)相比,此DAO方法的时间复杂度比O(1)高,因为您必须遍历角色列表。尽可能避免这种情况。
listOfRoles
但是,实际上,关系的所有者通常是标记级联的位置,并且通常会将所有级联。
Orphan Removal
如果删除与对象的所有关联,Hibernate可以为您解决。假设您有一个User具有的列表的,Role并且在此列表中有5个不同角色的链接。假设您删除了一个Role称为ROLE_EXAMPLE的对象,并且碰巧该ROLE_EXAMPLE在任何其他User对象上都不存在。如果您已orphanRemoval = true设置@OneToMany注释,则Hibernate将通过级联从数据库中删除现在“孤立”的Role对象。
ROLE_EXAMPLE
orphanRemoval = true
不应在所有情况下都启用孤立删除功能。实际上,在上面的示例中使用orphanRemoval没有任何意义。仅仅因为no User无法执行ROLE_EXAMPLE对象所代表的任何动作,并不意味着任何将来User都将永远无法执行该动作。
no User
该问答旨在补充正式的Hibernate文档,该文档针对这些关系具有大量的XML配置。
这些示例并非要复制粘贴到生产代码中。它们是如何使用JPA注释在Spring Framework中配置和配置Hibernate 4的通用示例。这些示例假定所有类都有以以下格式声明的ID字段:fooId。此ID字段的类型无关。