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

时间:2014-06-17 06:56:03

标签: java spring hibernate

我有两个课程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 个答案:

答案 0 :(得分:36)

使用注释创建关系

假设所有使用@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一起使用,该对象具有可以执行的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.
}

确定双向关系的哪一方拥有&#39;关系:

这是解决Hibernate关系的一个棘手方面,因为无论以何种方式建立关系,Hibernate都能正常运行。唯一会改变的是外键存储在哪个表中。通常,您拥有该集合的对象将拥有该关系。

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

确定您的fetchType

Lazily提取的集合导致了更多关于SO的问题而不是我要关注的问题,因为默认情况下Hibernate将懒洋洋地加载相关对象。根据Hibernate文档,如果关系是一对一或多对多关系并不重要:

  

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

考虑一下我对你的对象何时使用fetchType.LAZY vs 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可以在双向关系中以一种或两种方式级联操作。因此,如果您在Role上有User个列表,则可以在两个方向上将更改级联到Role。如果您更改Role上特定User的名称,则Hibernate会自动更新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的创建已插入数据库。然后,这些insert语句将能够使用User的PK作为其外键,因此您最终会得到N + 1个插入语句,其中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是RolelistOfRoles的数量,但是当Hibernate级联每个Role时,也会生成N个更新语句。 1}}到User表。这个DAO方法比我们之前的方法O(n)具有更高的时间复杂度,而不是O(1),因为你必须遍历角色列表。尽可能避免这种情况。

在实践中,通常情况下,关系的拥有方将是您标记级联的地方,并且您通常会级联所有内容。

孤儿清除

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

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

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

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

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

<子>声明: 我不知道这些例子是否适用于STANDALONE HIBERNATE

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

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