使用注释创建关系
假设所有类都用@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 对象时非常常用,该对象具有可以执行的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 列表。在大多数应用程序中,系统将比Roles 对象的实例更频繁地操作User 对象的实例。因此,我将使Role 对象成为关系的拥有方,并通过Role 的列表通过User 级联操作Role 对象。有关实际示例,请参阅双向一对多示例。通常,除非您有特殊要求,否则您将级联此方案中的所有更改。
确定您的 fetchType
延迟获取的集合在 SO 上导致的问题比我关心的要多,因为默认情况下 Hibernate 会延迟加载相关对象。根据 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 的代理。请参阅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 可以在双向关系中以一种或两种方式级联操作。因此,如果您在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。然后这些插入语句将能够使用 User 的 PK 作为它们的外键,因此您最终会得到 N + 1 个插入语句,其中 N 是用户列表中 Role 对象的数量。
相反,如果您想保存级联回 User 对象的单个 Role 对象,可以这样做:
//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 的数量,但还会生成 N 更新语句,因为 Hibernate 将每个 Role 添加到 User 表中.这种 DAO 方法比我们之前的方法具有更高的时间复杂度,O(n) 而不是 O(1),因为您必须遍历角色列表。尽可能避免这种情况。
然而,在实践中,通常关系的拥有方将是您标记级联的地方,并且您通常会级联所有内容。
孤儿删除
如果您删除与对象的所有关联,Hibernate 可以为您解决问题。假设您有一个User,他有一个Role 的列表,并且在这个列表中是指向5 个不同角色的链接。假设您删除了一个名为 ROLE_EXAMPLE 的Role,并且碰巧 ROLE_EXAMPLE 不存在于任何其他User 对象上。如果您在@OneToMany 注释上设置了orphanRemoval = true,Hibernate 将通过级联从数据库中删除现在“孤立”的角色对象。
不应该在所有情况下都启用孤立删除。事实上,在我们上面的例子中使用 orphanRemoval 是没有意义的。仅仅因为没有User 可以执行 ROLE_EXAMPLE 对象所代表的任何操作,这并不意味着任何未来的User 将永远无法执行该操作。
本问答旨在补充 Hibernate 官方文档,其中包含大量用于这些关系的 XML 配置。
这些示例并不是要复制粘贴到生产代码中。它们是有关如何使用 JPA 注释在 Spring Framework 中配置 Hibernate 4 来创建和管理各种对象及其关系的通用示例。这些示例假定所有类都具有以下格式声明的 ID 字段:fooId。此 ID 字段的类型不相关。
** 我们最近不得不放弃使用 Hibernate 来执行插入作业,在该作业中,我们通过集合将
免责声明:
我不知道这些示例是否适用于独立的 HIBERNATE
我与 Hibernate 或 Hibernate 开发团队没有任何关系。我提供了这些示例,以便在回答有关 Hibernate 标签的问题时可以参考。这些示例和讨论基于我自己的观点以及我如何使用 Hibernate 开发我的应用程序。这些例子绝不是全面的。我将它们基于我过去使用 Hibernate 的常见情况。
如果您在尝试实施这些示例时遇到问题,请不要发表评论并期待我解决您的问题。学习 Hibernate 的一部分是学习进出
的API。如果示例有错误,请随时编辑。