一尘不染

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

java

我有两个课程,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在处理集合时会导致a的原因以及如何解决问题?


阅读 372

收藏
2020-03-08

共1个答案

一尘不染

我有两个课程,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;
}

创建带注释的关系
假定所有带有@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。

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

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通过by级联Role上的列表处理对象User。有关实际示例,请参见双向“一对多”示例。通常,除非有特殊要求,否则您将级联此方案中的所有更改。

确定您的fetchType

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

默认情况下,Hibernate对集合使用延迟选择获取,对单值关联使用延迟代理获取。对于大多数应用程序中的大多数关联而言,这些默认设置有意义。

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

这样的性能优势是巨大的,并且只有在您向集合中添加更多对象时,它才会增长。这是因为对于急切加载的集合,Hibernate在后台进行了大量检查以确保您的数据都没有过期。虽然我确实主张将Hibernate用于集合,但是请注意,使用会降低性能**fetchType.EAGER。但是,以我们的Person对象为例。当我们加载a时,我们很可能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。有关更多信息,请参见文档。此方法的唯一缺点是您无法控制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个插入语句,其中NRole用户列表中对象的数量。

相反,如果要保存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更新语句RoleUser表。与您以前的方法O(n)相比,此DAO方法的时间复杂度比O(1)高,因为​​您必须遍历角色列表。尽可能避免这种情况。

但是,实际上,关系的所有者通常是标记级联的位置,并且通常会将所有级联。

Orphan Removal

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

不应在所有情况下都启用孤立删除功能。实际上,在上面的示例中使用orphanRemoval没有任何意义。仅仅因为no User无法执行ROLE_EXAMPLE对象所代表的任何动作,并不意味着任何将来User都将永远无法执行该动作。

该问答旨在补充正式的Hibernate文档,该文档针对这些关系具有大量的XML配置。

这些示例并非要复制粘贴到生产代码中。它们是如何使用JPA注释在Spring Framework中配置和配置Hibernate 4的通用示例。这些示例假定所有类都有以以下格式声明的ID字段:fooId。此ID字段的类型无关。

如何为这些类使用Hibernate 4的批注实现一个(单向/双向)一对多,多对一或多对多关系?

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

2020-03-08