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

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

问题描述

我有两个类, Foo Bar ,如下所示:

  public class Foo {
private Long fooId;

私人酒吧;
//是的,这实际上没有任何意义,
//这里有一个列表和一个对象,它就是一个例子。
私人列表< Bar>酒吧;
}

public class Bar {
private long barId;

私人Foo foo;



$ b $ p $如何实现一个单向/使用Hibernate 4的批注对这些类进行多对多,多对一或多对多的关系?



另外,如何配置我的一对多关系,很多为孤儿删除,延迟加载和什么导致 LazyInitialiaizationException 在处理集合和如何解决问题?

解决方案

使用注释创建关系



假定所有使用 @Entity @Table



单向一对一关系 strong>

  public class Foo {
private UUID fooId;

@OneToOne
私人酒吧;
}

public class Bar {
private UUID barId;
//没有相应的映射到Foo.class
}

由Foo.class管理的双向一对一关系

  public class Foo {
private UUID fooId;

@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name =barId)
私人酒吧;
}

public class Bar {
private UUID barId;

@OneToOne(mappedBy =bar)
private Foo foo;





使用用户管理连接表的单向一对多关系

  public class Foo {
private UUID fooId;
$ b @OneToMany
@JoinTable(name =FOO_BAR,
joinColumns = @JoinColumn(name =fooId),
inverseJoinColumns = @JoinColumn(name = barId))
私人列表< Bar>酒吧;
}

public class Bar {
private UUID barId;

//此处未指定映射。
}

@Entity
@Table(name =FOO_BAR)
public class FooBar {
private UUID fooBarId;

@ManyToOne
@JoinColumn(name =fooId)
private Foo foo;

@ManyToOne
@JoinColumn(name =barId)
私人酒吧;

//您可以在此处存储其他对象/字段。
}

非常常用于Spring Security设置用户对象,它们可以执行的列表角色。您可以添加和删除角色给用户,而无需担心级联删除角色的。



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

  public class Foo {
private UUID fooId;

@OneToMany(mappedBy =bar)
私人列表< Bar>酒吧;
}

public class Bar {
private UUID barId;

@ManyToOne
@JoinColumn(name =fooId)
private Foo foo;



$ b $双向多对多使用Hibernate托管连接表 strong>

  public class Foo {
private UUID fooId;
$ b @OneToMany
@JoinTable(name =FOO_BAR,
joinColumns = @JoinColumn(name =fooId),
inverseJoinColumns = @JoinColumn(name = barId))
私人列表< Bar>酒吧;
}

public class Bar {
private UUID barId;
$ b @OneToMany
@JoinTable(name =FOO_BAR,
joinColumns = @JoinColumn(name =barId),
inverseJoinColumns = @JoinColumn(name = fooId))
private List< Foo> FOOS;





双向多对多使用用户管理的连接表对象
$ b 通常用于存储关于连接对象的额外信息,例如创建关系的日期。

  public class Foo {
private UUID fooId;

@OneToMany(mappedBy =bar)
private List< FooBar>酒吧;
}

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)
私人酒吧;

//您可以在此处存储其他对象/字段。
}



确定双向关系拥有关系的哪一侧:



这是解决Hibernate关系的棘手方面之一,因为不论建立关系的方式如何,Hibernate都能正常运行。唯一会改变的是外键存储在哪个表中。一般来说,你有一个集合的对象将拥有这个关系。



例子:一个用户对象有一个列表角色在其上声明。在大多数应用程序中,系统将比 Roles 对象的实例更经常地操作 User 对象的实例。因此,我会让 Role 对象成为关系的拥有者,并通过<$ $的列表操作 Role 对象c $ c>角色在一个用户级联。有关实际示例,请参阅双向One to Many 示例。通常情况下,您将级联本场景中的所有更改,除非您有特定的要求。否则。



确定您的fetchType



<由于默认情况下Hibernate会懒惰地加载相关对象,所以懒得提取集合导致了更多的问题。根据Hibernate文档,关系是一对一还是多对多并不重要:


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


考虑这个问题我的两个分钟是什么时候使用 fetchType.LAZY vs fetchType.EAGER 。如果你知道有50%的时间你不需要访问父对象的集合,我会使用 fetchType.LAZY

性能优势非常巨大,只有在向对象中添加更多对象时才会增长。这是因为对于一个急切加载的集合,Hibernate会进行大量的幕后检查,以确保您的数据都不会过期。虽然我主张使用Hibernate进行集合,但请注意,使用 fetchType.EAGER 时性能会受到影响 ** 。但是,请参阅我们的 Person 对象示例。当我们加载 Person 时,我们很可能想知道它们执行的是什么 Roles 。我通常会将此集合标记为 fetchType.EAGER 不要将您的收藏标记为 fetchType.EAGER 只需简单地获取 LazyInitializationException 不仅因为性能方面的原因,它通常表明您有设计问题。问问你自己,这个集合应该是一个急切加载的集合,还是我这样做只是为了在这个方法中访问集合。 Hibernate有解决这个问题的办法,这并不会影响你的操作性能。您可以在服务图层中使用以下代码,只需要为此次调用初始化一个延迟加载的集合即可。

  //服务类
@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可以在bi中以一种方式或两种方式级联操作所以如果你在 User 上有一个 Role 的列表,你可以将更改级联到角色在两个方向上。如果您在 User 上更改特定 Role 的名称,Hibernate可以自动更新关联的角色角色表。



然而,这并不总是期望的行为。如果你想一想,在这种情况下,根据对 User 的更改对 Role 进行更改不会有道理。然而,这是有道理的,但方向却相反。在角色对象本身上更改角色的名称,并且该更改可以级联到所有 User 具有 Role 的对象。

在效率方面,通过保存创建/更新角色 >用户它们所属的对象。这意味着你会将 @OneToMany 注释标记为级联标注。我将举一个例子:

  public User saveOrUpdate(User user){
getCurrentSession.saveOrUpdate(user);
返回用户;



$ b在上面的例子中,Hibernate会生成一个 INSERT 查询用户对象,然后级联创建角色的一次已将 User 插入到数据库中。这些插入语句将能够使用 User 的PK作为它们的外键,所以你最终会得到N + 1个插入语句,其中N是角色用户列表中的对象。



相反,如果您要保存单个将对象级联回用户对象,可以这样做:

  //假设用户在列表中没有任何角色,但已经以1插入的代价保存到
//数据库。
public void saveOrUpdateRoles(User role,List< Roles> listOfRoles){
for(Role role:listOfRoles){
role.setUser(user);
getCurrentSession.saveOrUpdate(role);


$ / code>

这导致N + 1插入,其中N是在 listOfRoles Role 的数量,而且随着Hibernate级联每个角色添加到用户表中。这个DAO方法的时间复杂度比我们以前的方法O(n)要高,而不是O(1),因为你必须迭代角色列表。如果可能的话,尽量避免这种情况。在实践中,通常情况下,关系的拥有方将是您标记级联的地方,而且通常会级联所有级别。

p>

孤儿删除



如果删除所有关联到对象,Hibernate可以为您解决问题。假设您有一个用户,他有一个角色的列表,并且列表中有5个不同角色的链接。假设你删除了一个名为ROLE_EXAMPLE的 Role ,并且碰巧ROLE_EXAMPLE在任何其他的 User 对象中都不存在。如果你在 @OneToMany 注释中设置了 orphanRemoval = true ,Hibernate会从当前'孤立'角色对象中删除数据库级联。



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



此Q& A旨在补充官方Hibernate文档,这些文档具有大量用于这些关系的XML配置。

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



**我们最近不得不放弃使用Hibernate来插入作业,在那里插入< 80,000+个对象通过集合进入数据库。 Hibernate吃掉了所有堆内存,检查集合并使系统崩溃。
$ b 免责声明:
我不知道如果这些例子将与STANDALONE HIBERNATE一起工作
我不以任何方式隶属于Hibernate或Hibernate开发团队。我提供了这些示例,因此我有一个参考指向何时回答有关Hibernate标记的问题。这些例子和讨论是基于我自己的观点以及我如何使用Hibernate开发我的应用程序。这些例子并不全面。我基于他们过去使用过Hibernate的常见情况。

如果遇到试图实现这些示例的问题,不要评论,并期望我解决您的问题。学习的一部分Hibernate正在学习它的API的
。如果这些示例出现错误,请随时编辑它们。


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

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?

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?

解决方案

Creating Relationships with Annotations

Assume all classes annotated with @Entity and @Table

Uni-directional One to One Relationship

public class Foo{
    private UUID fooId;

    @OneToOne
    private Bar bar;
}

public class Bar{
    private UUID barId;
    //No corresponding mapping to Foo.class
}

Bi-Directional One to One Relationship managed by 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;
}

Uni-Directional One to Many Relationship using user managed join table

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

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.

Bi-directional One to Many Relationship using foreign key mapping

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

Bi-Directional Many to Many using Hibernate managed join table

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

Bi-Directional Many to Many using user managed join table object

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

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.

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.

Determining your fetchType

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:

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.

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.

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

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

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

Determining Cascade Direction

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.

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.

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

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.

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

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.

Orphan Removal

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.

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.

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

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.

** We recently had to abandon using Hibernate for an insert job where we were inserting <80,000+ objects into the database through a collection. Hibernate ate up all the heap memory doing checking on the collection and crashed the system.

DISCLAIMER: I DO NOT KNOW IF THESE EXAMPLES WILL WORK WITH STANDALONE HIBERNATE

I am not in any way affiliated with Hibernate or the Hibernate dev team. I'm providing these examples so I have a reference to point to when I'm answering questions on the Hibernate tag. These examples and discussions are my based on my own opinion as well as how I develop my applications using Hibernate. These examples are in no way comprehensive. I'm basing them on the common situations I've used Hibernate for in the past.

If you encounter issues trying to implement these examples, do not comment and expect me to fix your problem. Part of learning Hibernate is learning the in's and out 's of its API. If there is a mistake with the examples, please feel free to edit them.

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

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