如何在 Hibernate 4 和 Spring 中使用注解来定义不同类型的关系? [英] How do I use annotations to define different types of relationships in Hibernate 4 and Spring?

查看:18
本文介绍了如何在 Hibernate 4 和 Spring 中使用注解来定义不同类型的关系?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有两个类,FooBar,如下:

I have two classes, Foo and Bar, as follows:

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 的注释为这些类实现(单向/双向)一对多、多对一或多对多关系?

How do I implement a (uni-directional/bi-directional) one-to-many, many-to-one or many-to-many relationship using annotations for Hibernate 4 for these classes?

另外,我如何配置一对多的孤儿移除、延迟加载以及在处理集合时导致 LazyInitialiaizationException 的原因以及如何解决问题?

Also how do I configure my one-to-many for orphan removal, lazy loading and what causes a LazyInitialiaizationException when dealing with collections and how to solve the problem?

推荐答案

使用注解创建关系

假设所有用@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.
}

在设置具有他们可以执行的 Role 列表的 User 对象时,非常常与 Spring Security 一起使用.您可以为用户添加和删除角色,而不必担心级联删除 Role 的问题.

Very commonly used with Spring Security when setting up a User object who has a list of Role's that they can perform. You can add and remove roles to a user without having to worry about cascades deleting Role's.

使用外键映射的双向一对多关系

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;
}

使用用户管理的连接表对象的双向多对多

通常用于在连接对象上存储额外信息,例如关系创建日期.

Commonly used when you want to store extra information on the join object such as the date the relationship was created.

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 都会正确运行.唯一会改变的是外键存储在哪个表上.通常,您拥有集合的对象将拥有该关系.

Determining which side of the bi-directional relationship 'owns' the relationship:

This is one of the trickier aspects of working out Hibernate relationships because Hibernate will operate correctly no matter which way to set up the relationship. The only thing that will change is which table the foreign key is stored on. Generally the object that you have a collection of will own the relationship.

示例:User 对象具有声明的 Roles 列表.在大多数应用程序中,系统将比 Roles 对象的实例更频繁地操作 User 对象的实例.因此,我将使 Role 对象成为关系的拥有方,并通过 Role 上的列表操作 Role 对象>User 级联.有关实际示例,请参阅双向一对多示例.通常,除非您有特殊要求,否则您将在此场景中级联所有更改.

Example: A User object has a list of Roles declared on it. In most applications, the system will be manipulating instances of the User object more often than instances of the Roles object. Hence I would make the Role object the owning side of the relationship and manipulate the Role objects through the list of Role's on a User by cascade. For a practical example see the bi-directional One to Many example. Typically you will cascade all changes in this scenario unless you have a specific requirement to do otherwise.

延迟获取的集合在 SO 上导致的问题比我想看到的要多,因为默认情况下 Hibernate 会延迟加载相关的对象.根据 Hibernate 文档,关系是一对一还是多对多并不重要:

Lazily fetched collections have resulted in more issues on SO than I care to look at because by default Hibernate will load related objects lazily. It doesn't matter if the relationship is a one-to-one or many-to-many as per the Hibernate docs:

默认情况下,Hibernate 对集合使用惰性选择获取,对单值关联使用惰性代理获取.这些默认值对大多数应用程序中的大多数关联都有意义.

By default, Hibernate uses lazy select fetching for collections and lazy proxy fetching for single-valued associations. These defaults make sense for most associations in the majority of applications.

考虑一下我何时在对象上使用 fetchType.LAZYfetchType.EAGER 的两分钱.如果您知道 50% 的时间不需要访问父对象上的集合,我会使用 fetchType.LAZY.

Consider this my two cents on when to use fetchType.LAZY vs fetchType.EAGER on your objects. If you know that 50% of the time you won't need to access the collection on your parent object, I'd be using fetchType.LAZY.

由此带来的性能优势是巨大的,并且只会随着您向集合中添加更多对象而增长.这是因为对于急切加载的集合,Hibernate 会进行大量的幕后检查以确保您的任何数据都没有过时.虽然我确实提倡使用 Hibernate 进行集合,但请注意使用 fetchType.EAGER 会降低性能**.但是,以我们的 Person 对象为例.很可能当我们加载一个 Person 时,我们会想知道他们执行什么 Roles.我通常会将这个集合标记为 fetchType.EAGER.不要反射性地将您的集合标记为 fetchType.EAGER 只是为了绕过 LazyInitializationException. 这不仅出于性能原因不好,而且通常表示您有设计问题.问问自己,这个集合是否真的是一个急切加载的集合,还是我这样做只是为了以这种方法访问集合.Hibernate 有解决此问题的方法,它不会对您的操作性能产生太大影响.如果你想为这个调用初始化一个延迟加载的集合,你可以在 Service 层中使用以下代码.

The performance benefits are of this are huge and only grow as you add more objects to your collection. This is because for an eagerly loaded collection, Hibernate does a ton of behind the scenes checking to ensure that none of your data is out of date. While I do advocate using Hibernate for collections, be aware that there is a performance penalty** for using fetchType.EAGER. However, take our Person object example. Its fairly likely that when we load a Person we will want to know what Roles they perform. I will usually mark this collection as fetchType.EAGER. DON'T REFLEXIVELY MARK YOUR COLLECTION AS fetchType.EAGER SIMPLY TO GET AROUND A LazyInitializationException. Not only is it bad for performance reasons, it generally indicates that you have a design issue. Ask yourself, should this collection actually be an eagerly loaded collection, or am I doing this just to access the collection in this one method. Hibernate has ways around this, that doesn't impact the performance of your operations quite as much. You can use the following code in your Service layer if you want to initialize a lazily loaded collection just for this one call.

//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 中进行.

The call to Hibernate.initialize forces the creation and loading of the collection object. However, be careful, if you only pass it the Person instance, you will get a proxy of your Person back. See the documentation for more information. The only downside to this method is that you have no control over how Hibernate will actually fetch your collection of objects. If you want to control this, then you can do so in your 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.如果您真的感兴趣,链接的答案会更详细.

The performance here depends on what FetchMode you specify. I've read answers that say to use FetchMode.SUBSELECT for performance reasons. The linked answer goes into more detail if you are really interested.

如果你想在我重复的时候阅读我,请随时查看我的其他答案 这里

If you want to read me as I repeat myself, feel free to check out my other answer here

Hibernate 可以在双向关系中以一种或两种方式级联操作.因此,如果您在 User 上有一个 Role 的列表,您可以在两个方向上对 Role 进行级联更改.如果您更改了 User 上特定 Role 的名称,Hibernate 可以自动更新 Role 上关联的 Role表.

Hibernate can cascade operations either or both ways in a bi-directional relationship. So if you have a List of Role's on a User you can cascade changes to Role's in both directions. If you change the name of a particular Role on a User Hibernate can automatically update the associated Role on the Role Table.

然而,这并不总是理想的行为.如果您考虑一下,在这种情况下,根据对 User 的更改来更改 Role 没有任何意义.然而,朝着相反的方向前进是有道理的.在 Role 对象本身上更改 Role 的名称,并且该更改可以级联到具有该 Role 的所有 User 对象 就可以了.

However this is not always desired behaviour. If you think about it, in this case, making changes to Role's based on changes to User doesn't make any sense. However it makes sense going in the opposite direction. Change a Role's name on the Role object itself, and that change can be cascaded to all User objects that have that Role on it.

就效率而言,通过保存它们所属的 User 对象来创建/更新 Role 对象是有意义的.这意味着您可以将 @OneToMany 注释标记为级联注释.我举个例子:

In terms of efficiency, it makes sense to create/update Role objects by saving the User object that they belong to. This means you would mark your @OneToMany annotation as the cascading one. I'll give an example:

public User saveOrUpdate(User user){
    getCurrentSession.saveOrUpdate(user);
    return user;
}

在上面的例子中,Hibernate会为User对象生成一个INSERT查询,然后级联创建Role的一旦 User 被插入到数据库中.然后这些插入语句将能够使用 User 的 PK 作为它们的外键,因此您最终会得到 N + 1 个插入语句,其中 N 是 Role 用户列表中的对象.

In the above example, Hibernate will generate a INSERT query for the User object, and then cascade the creation of the Role's once the User has been inserted into the database. These insert statements will then be able to use the PK of the User as their foreign key, so you would end up with N + 1 insert statements, where N is the number of Role objects in the list of users.

相反,如果您想保存级联回 User 对象的单个 Role 对象,可以这样做:

Conversely if you wanted to save the individual Role objects cascading back to the User object, could be done:

//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 表.这种 DAO 方法比我们之前的方法具有更高的时间复杂度,O(n) 而不是 O(1),因为您必须遍历角色列表.尽可能避免这种情况.

This results in N + 1 inserts where N is the number of Role's in the listOfRoles, but also N update statements being generated as Hibernate cascades the addition of each Role to the User table. This DAO method has a higher time complexity than our previous method, O(n) as opposed to O(1) because you have to iterate through the list of roles. Avoid this if at all possible.

然而,在实践中,通常关系的拥有方是您标记级联的地方,您通常会级联所有内容.

In practice however, usually the owning side of the relationship will be where you mark your cascades, and you will usually cascade everything.

如果您删除与某个对象的所有关联,Hibernate 可以为您解决问题.假设你有一个 User,他有一个 Role 的列表,在这个列表中是指向 5 个不同角色的链接.假设您删除了一个名为 ROLE_EXAMPLE 的 Role 并且碰巧 ROLE_EXAMPLE 不存在于任何其他 User 对象上.如果您在 @OneToMany 注释上设置了 orphanRemoval = true,Hibernate 将通过级联从数据库中删除现在孤立"的 Role 对象.

Hibernate can work out for you if you remove all associations to an object. Suppose you have a User who has a list of Role's and in this list are links to 5 different roles. Lets say you remove a Role called ROLE_EXAMPLE and it happens that the ROLE_EXAMPLE doesn't exist on any other User object. If you have orphanRemoval = true set on the @OneToMany annotation, Hibernate will delete the now 'orphaned' Role object from the database by cascade.

不应该在所有情况下都启用孤儿移除.事实上,在我们上面的例子中使用 orphanRemoval 是没有意义的.仅仅因为没有 User 可以执行 ROLE_EXAMPLE 对象表示的任何操作,这并不意味着任何未来的 User 将永远无法执行该操作.

Orphan removal should not be enabled in every case. In fact, having orphanRemoval in our example above makes no sense. Just because no User can perform whatever action the ROLE_EXAMPLE object is representing, that doesn't mean that any future User will never be able to perform the action.

本问答旨在补充官方 Hibernate 文档,该文档对这些关系进行了大量 XML 配置.

This Q&A is intended to complement the official Hibernate documentation, which has a large amount of XML configuration for these relationships.

这些示例并不是要复制粘贴到生产代码中.它们是如何使用 JPA 注释在 Spring 框架内配置 Hibernate 4 来创建和管理各种对象及其关系的通用示例.这些示例假定所有类都具有以下格式声明的 ID 字段:fooId.此 ID 字段的类型不相关.

These examples are not meant to be copy-pasted into production code. They are generic examples of how to create and manage various objects and their relationships using JPA annotations to configure Hibernate 4 within the Spring Framework. The examples assume that all classes have ID fields declared in the following format: fooId. The type of this ID field is not relevant.

** 我们最近不得不放弃使用 Hibernate 进行插入作业,在该作业中,我们通过集合将 <80,000+ 个对象插入到数据库中.Hibernate 在检查集合时消耗了所有的堆内存并导致系统崩溃.

免责声明:我不知道这些示例是否适用于独立休眠

我与 Hibernate 或 Hibernate 开发团队没有任何关系.我提供了这些示例,以便我在回答有关 Hibernate 标签的问题时有一个参考.这些示例和讨论基于我自己的观点以及我如何使用 Hibernate 开发我的应用程序.这些例子并不全面.我将它们基于过去使用 Hibernate 的常见情况.

如果您在尝试实现这些示例时遇到问题,请不要发表评论并期望我解决您的问题.学习 Hibernate 的一部分是学习进进出出是它的 API.如果示例中有错误,请随时编辑.

这篇关于如何在 Hibernate 4 和 Spring 中使用注解来定义不同类型的关系?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆