我有两个课程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
的原因以及如何解决问题?
答案 0 :(得分:36)
假设所有使用@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一起使用,该对象具有可以执行的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;
}
使用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
上的Role
列表操纵User
个对象。有关实际示例,请参阅双向一对多示例。通常,您将在此方案中级联所有更改,除非您有特定要求,否则。
Lazily提取的集合导致了更多关于SO的问题而不是我要关注的问题,因为默认情况下Hibernate将懒洋洋地加载相关对象。根据Hibernate文档,如果关系是一对一或多对多关系并不重要:
默认情况下,Hibernate对集合使用延迟选择提取,对单值关联使用延迟代理提取。这些默认值对大多数应用程序中的大多数关联都有意义。
考虑一下我对你的对象何时使用fetchType.LAZY
vs 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
的代理。有关详细信息,请参阅documentation。这种方法的唯一缺点是你无法控制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
。我已阅读answers出于性能原因使用FetchMode.SUBSELECT
的说法。如果您真的感兴趣,链接的答案会更详细。
如果您想重读自己,请随时查看我的其他答案here
Hibernate可以在双向关系中以一种或两种方式级联操作。因此,如果您在Role
上有User
个列表,则可以在两个方向上将更改级联到Role
。如果您更改Role
上特定User
的名称,则Hibernate会自动更新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
的创建已插入数据库。然后,这些insert语句将能够使用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是Role
中listOfRoles
的数量,但是当Hibernate级联每个Role
时,也会生成N个更新语句。 1}}到User
表。这个DAO方法比我们之前的方法O(n)具有更高的时间复杂度,而不是O(1),因为你必须遍历角色列表。尽可能避免这种情况。
在实践中,通常情况下,关系的拥有方将是您标记级联的地方,并且您通常会级联所有内容。
如果删除对象的所有关联,Hibernate可以为您解决问题。假设您的User
列表中包含Role
,并且此列表中包含指向5个不同角色的链接。假设您删除了一个名为ROLE_EXAMPLE的Role
,并且ROLE_EXAMPLE在任何其他User
对象上都不存在。如果您在orphanRemoval = true
注释上设置@OneToMany
,Hibernate将删除现在的&#39;孤儿&#39;通过级联从数据库中的角色对象。
不应在每种情况下都启用孤儿删除。实际上,在上面的示例中使用orphanRemoval是没有意义的。仅仅因为没有User
可以执行ROLE_EXAMPLE对象所代表的任何操作,这并不意味着任何未来User
将永远无法执行该操作。
本Q&amp; A旨在补充官方Hibernate文档,该文档为这些关系提供了大量XML配置。
这些示例并不打算复制粘贴到生产代码中。它们是如何使用JPA注释在Spring Framework中配置Hibernate 4来创建和管理各种对象及其关系的通用示例。这些示例假定所有类都具有以下列格式声明的ID字段:fooId
。此ID字段的类型不相关。
**我们最近不得不放弃使用Hibernate进行插入作业,我们通过集合将&lt; 80,000+个对象插入到数据库中。 Hibernate在检查集合时占用了所有堆内存,并使系统崩溃。
<子>声明: 我不知道这些例子是否适用于STANDALONE HIBERNATE
我与Hibernate或Hibernate开发团队没有任何关系。我提供了这些示例,因此我可以参考指出我何时回答有关Hibernate标记的问题。这些示例和讨论是基于我自己的观点以及如何使用Hibernate开发应用程序。这些例子绝不是全面的。我基于我过去使用过Hibernate的常见情况。
如果您在尝试实施这些示例时遇到问题,请不要发表评论并希望我解决您的问题。学习Hibernate的部分原因是学习和学习 其API的API。如果示例有错误,请随时编辑。