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

2022-09-02 20:09:42

我有两个类,和 ,如下所示: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的注释实现(单向/双向)一对多,多对一或多对多关系?

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


答案 1

使用批注创建关系

假设所有类都带有@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.
}

在设置具有可以执行的列表的对象时,通常与Spring Security一起使用。您可以向用户添加和删除角色,而不必担心级联删除 。UserRoleRole

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

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

使用休眠托管联接表的双向多对多

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

示例:对象上有一个声明的列表。在大多数应用程序中,系统将比对象的实例更频繁地操作对象的实例。因此,我会使对象成为关系的拥有方,并通过级联上的 列表来操作对象。有关实际示例,请参阅双向一对多示例。通常,您将级联此方案中的所有更改,除非您有特定的要求来执行其他操作。UserRolesUserRolesRoleRoleRoleUser

确定您的抓取类型

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

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

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

性能优势是巨大的,并且只会随着向集合中添加更多对象而增长。这是因为对于热切加载的集合,Hibernate会进行大量的幕后检查,以确保您的任何数据都不会过期。虽然我确实提倡对集合使用 Hibernate,但请注意,使用 会对性能造成影响**。但是,以我们的对象为例。当我们加载时,我们很可能想知道它们的性能。我通常会将此集合标记为 .不要条件反射地将你的集合标记为 fetchType.EAGER 只是为了绕过一个 LazyInitializationException这不仅是出于性能原因造成的不良影响,而且通常表明您存在设计问题。问问自己,这个集合是否真的是一个热切加载的集合,或者我这样做只是为了在这个方法中访问集合。Hibernate有办法解决这个问题,这不会对你的运营性能产生太大的影响。如果要仅为这一次调用初始化延迟加载的集合,则可以在层中使用以下代码。fetchType.EAGERPersonPersonRolesfetchType.EAGERService

//Service Class
@Override
@Transactional
public Person getPersonWithRoles(UUID personId){
    Person person = personDAO.find(personId);

    Hibernate.initialize(person.getRoles());

    return person;
}

强制创建和加载集合对象的调用。但是,请注意,如果您仅将其传递到实例,您将获得背面的代理。有关详细信息,请参阅文档。此方法的唯一缺点是,您无法控制Hibernate如何实际获取对象集合。如果你想控制这一点,那么你可以在你的DAO中这样做。Hibernate.initializePersonPerson

//DAO
@Override
public Person findPersonWithRoles(UUID personId){
    Criteria criteria = sessionFactory.getCurrentSession().createCritiera(Person.class);

    criteria.add(Restrictions.idEq(personId);
    criteria.setFetchMode("roles", FetchMode.SUBSELECT);
}

此处的性能取决于您指定的内容。我已经阅读了答案,说出于性能原因而使用。如果您真的感兴趣,链接的答案会更详细地介绍。FetchModeFetchMode.SUBSELECT

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

确定级联方向

休眠可以在双向关系中以两种方式级联操作。因此,如果您在 上有 一个 列表,则可以在两个方向上级联更改为 的 。如果更改特定上某个人的名称,则休眠器可以自动更新表上的关联。RoleUserRoleRoleUserRoleRole

然而,这并不总是期望的行为。如果您仔细想想,在这种情况下,根据对 的更改对 进行更改没有任何意义。然而,朝着相反的方向前进是有道理的。更改对象本身的名称,该更改可以级联到所有具有该名称的对象。RoleUserRoleRoleUserRole

在效率方面,通过保存对象所属的对象来创建/更新对象是有意义的。这意味着您会将注释标记为级联注释。我举个例子:RoleUser@OneToMany

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

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

相反,如果要保存级联回对象的各个对象,可以执行以下操作:RoleUser

//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 是 中的数目,但当休眠将每个语句级联到表中时,也会生成 N 个更新语句。与 O(1) 相比,此 DAO 方法具有比我们以前的方法 O(n) 更高的时间复杂度,因为您必须遍历角色列表。如果可能的话,避免这样做。RolelistOfRolesRoleUser

然而,在实践中,通常关系的拥有方将是你标记级联的地方,你通常会级联所有的东西。

孤儿删除

如果您删除与对象的所有关联,则休眠可以为您解决。假设您有一个谁有一个列表,并且在此列表中有指向5个不同角色的链接。假设您删除了一个被调用ROLE_EXAMPLE并且碰巧ROLE_EXAMPLE不存在于任何其他对象上。如果已对注释进行设置,Hibernate 将通过级联从数据库中删除现在“孤立”的 Role 对象。UserRoleRoleUserorphanRemoval = true@OneToMany

不应在每种情况下都启用孤立项删除。事实上,在上面的示例中使用 orphanRemoval 是没有意义的。仅仅因为 no 可以执行ROLE_EXAMPLE对象所代表的任何操作,这并不意味着任何未来都永远无法执行该操作。UserUser

本问答旨在补充官方Hibernate文档,该文档为这些关系提供了大量的XML配置。

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

** 我们最近不得不放弃使用Hibernate进行插入作业,通过集合将<80,000多个对象插入到数据库中。休眠吞噬了所有对集合进行检查的堆内存,并使系统崩溃。

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

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

如果您在尝试实现这些示例时遇到问题,请不要发表评论,并希望我解决您的问题。学习Hibernate的一部分是学习其API的输入和输出。如果示例有误,请随时编辑它们。


答案 2

推荐