繁体   English   中英

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

[英]How do I use annotations to define different types of relationships in Hibernate 4 and Spring?

我有两个类, 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原因以及如何解决问题?

使用注释创建关系

假设所有类都注释了@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

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

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通过的对象列表Role的一个User通过级联。 有关实际示例,请参阅双向一对多示例。 通常,您将在此方案中级联所有更改,除非您有特定要求。

确定你的fetchType

由于默认情况下Hibernate将懒惰地加载相关对象,因此Lazily获取的集合导致了更多关于SO的问题。 根据Hibernate文档,这种关系是一对一还是多对多并不重要:

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

考虑一下我在你的对象上使用fetchType.LAZYfetchType.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的代理。 有关更多信息,请参阅文档 这种方法的唯一缺点是你无法控制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可以在双向关系中以一种或两种方式级联操作。 因此,如果您在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 然后,这些insert语句将能够使用User的PK作为其外键,因此您最终会得到N + 1个insert语句,其中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是listOfRolesRole的数量,但是当Hibernate级联将每个RoleUser表时,也会生成N个更新语句。 这个DAO方法比我们之前的方法O(n)具有更高的时间复杂度,而不是O(1),因为你必须遍历角色列表。 尽可能避免这种情况。

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

孤儿去除

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

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

此Q&A旨在补充官方Hibernate文档,该文档为这些关系提供了大量XML配置。

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

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

免责声明:我不知道这些示例是否与STANDALONE HIBERNATE合作

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

如果您在尝试实施这些示例时遇到问题,请不要发表评论并希望我解决您的问题。 学习Hibernate的一部分就是学习它的API。 如果示例中存在错误,请随时编辑它们。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM