Java 休眠多对多 - 获取方法渴望与懒惰

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/24150199/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 10:30:15  来源:igfitidea点击:

Hibernate many to many - fetch method eager vs lazy

javahibernateormmany-to-manyhibernate-mapping

提问by john Smith

New to Hibernate.

Hibernate 的新手。

I have User Group many to many relation. Three tables : User , Group and UserGroup mapping table.

我有用户组多对多关系。三个表: User 、 Group 和 UserGroup 映射表。

Entities:

实体:

@Entity
@Table(name = "user")
public class User {

@Id
@Column (name = "username")
private String userName;

@Column (name = "password", nullable = false)
private String password;


@ManyToMany(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
@JoinTable(name="usergroup", 
            joinColumns={@JoinColumn(name="username")}, 
            inverseJoinColumns={@JoinColumn(name="groupname")})
private Set<Group> userGroups = new HashSet<Group>();

... setter and getters



@Entity
@Table(name = "group")
public class Group {

@Id
@Column(name = "groupname")
private String groupName;

@Column(name = "admin", nullable = false)
private String admin;

@ManyToMany(mappedBy = "userGroups", fetch = FetchType.EAGER)
private Set<User> users = new HashSet<User>();

... setter and getters

Notice that in Group Entity I'm using fetch method EAGER. Now, when I'm calling my DAO to retrive all the groups in the system using the following criteria:

请注意,在 Group Entity 中,我使用了 fetch 方法 EAGER。现在,当我调用我的 DAO 以使用以下条件检索系统中的所有组时:

  Criteria criteria = session.createCriteria(Group.class);
  return criteria.list();

I'm getting all the rows from the mappgin table (usergroup) instead of getting the actual number of groups...

我从 mappgin 表(用户组)中获取所有行,而不是获取实际的组数...

for example if i have in user table

例如,如果我在用户表中

 username password
 -----------------
 user1     user1
 user2     user2

in group table

在组表中

 groupname admin
 ---------------
 grp1      user1
 grp2      user2

in usergroup table

在用户组表中

 username groupname
 ------------------
 user1     grp1
 user2     grp2
 user1     grp2
 user2     grp1

The result will be the following list - {grp1,grp2,grp2,grp1}

结果将是以下列表 - {grp1,grp2,grp2,grp1}

Instead of {grp1,grp2}

而不是 {grp1,grp2}

If I change in Group Entity the fetch method to LAZY I'm getting the correct results but hibernate throws me LazyException in another place...

如果我在 Group Entity 中将 fetch 方法更改为 LAZY,我会得到正确的结果,但是 hibernate 会在另一个地方抛出我的 LazyException ......

Please assist what fetch method should I use and why ?

请帮助我应该使用什么获取方法,为什么?

Thanks!

谢谢!

采纳答案by JamesENL

Lazy people will tell you to always use FetchType.EAGERcounter-intuitively. These are the people who generally don't worry about database performance and only care about making their development lives easier. I'm going to say you should be using FetchType.LAZYfor the increased performance benefit. Because database access is usually the performance bottleneck of most applications, every little bit helps.

懒惰的人会告诉你总是FetchType.EAGER反直觉地使用。这些人通常不担心数据库性能,只关心让他们的开发生活更轻松。我会说你应该使用它FetchType.LAZY来提高性能。因为数据库访问通常是大多数应用程序的性能瓶颈,所以一点点帮助。

If you do actually need to get a list of users for a group, as long as your call getUsers()from within a transactional session, you won't get that LazyLoadingExceptionthat is the bane of all new Hibernate users.

如果您确实需要获取一个组的用户列表,只要您getUsers()在事务会话中调用,您就不会知道LazyLoadingException这是所有新 Hibernate 用户的祸根。

The following code will get you all groups without populating the list of users for those groups

以下代码将为您提供所有组,而无需填充这些组的用户列表

//Service Class
@Transactional
public List<Group> findAll(){
    return groupDao.findAll();
}

The following code will get you all groups with the users for those groups at the DAO level:

以下代码将为您提供所有组以及 DAO 级别的这些组的用户:

//DAO class
@SuppressWarnings("unchecked")
public List<Group> findAllWithUsers(){
    Criteria criteria = getCurrentSession().createCriteria(Group.class);

    criteria.setFetchMode("users", FetchMode.SUBSELECT);
    //Other restrictions here as required.

    return criteria.list();
}

Edit 1: Thanks to Adrian Shum for this code

编辑 1:感谢 Adrian Shum 提供此代码

For more info on the different types of FetchMode's see here

有关不同类型的更多信息,FetchMode请参见此处

If you don't want to have to write a different DAO method just to access your collection object, as long as you are in the same Sessionthat was used to fetch the parent object you can use the Hibernate.initialize()method to force the initialisation of your child collection object. I would seriously not recommend that you do this for a List<T>of parent objects. That would put quite a heavy load on the database.

如果您不想为了访问您的集合对象而编写不同的 DAO 方法,只要您与Session用于获取父对象的Hibernate.initialize()方法相同,您就可以使用该方法强制初始化您的子集合目的。我真的不建议您List<T>为父对象执行此操作。这会给数据库带来相当大的负载。

//Service Class
@Transactional
public Group findWithUsers(UUID groupId){
    Group group = groupDao.find(groupId);

    //Forces the initialization of the collection object returned by getUsers()
    Hibernate.initialize(group.getUsers());

    return group;
}

I've not come across a situation where I've had to use the above code, but it should be relatively efficient. For more information about Hibernate.initialize()see here

我还没有遇到过不得不使用上述代码的情况,但它应该是相对高效的。有关更多信息,Hibernate.initialize()请参见此处

I have done this in the service layer rather than fetching them in the DAO, because then you only have to create one new method in the service rather than making a separate DAO method as well. The important thing is that you have wrapped the getUsers()call within the transaction, so a session will have been created that Hibernate can use to run the additional queries. This could also be done in the DAO by writing join criteria to your collection, but I've never had to do that myself.

我在服务层完成了这个,而不是在 DAO 中获取它们,因为那样你只需要在服务中创建一个新方法,而不是创建一个单独的 DAO 方法。重要的是您已将getUsers()调用包装在事务中,因此将创建一个会话,Hibernate 可以使用该会话来运行其他查询。这也可以在 DAO 中通过为您的集合编写连接标准来完成,但我自己从来没有这样做过。

That said, if you find that you are calling the second method far more than you are calling the first, consider changing your fetch type to EAGERand letting the database do the work for you.

也就是说,如果您发现调用第二种方法的次数远远多于调用第一种方法,请考虑将提取类型更改为EAGER并让数据库为您完成工作。

回答by Adrian Shum

Although answer from JamesENL is almost correct, it is lacking of some very key aspect.

虽然 JamesENL 的回答几乎是正确的,但它缺少一些非常关键的方面。

What he is doing is to force the lazy-loading proxy to load when the transaction is still active. Although it solved the LazyInitialization error, the lazy loadings are still going to be done one by one, which is going to give extremely poor performance. Essentially, it is simply achieving the same result of FetchType.EAGER manually (and with a even worse way, because we missed the possibilities of using JOIN and SUBSELECT strategy), which even contradict with the concern of performance.

他正在做的是强制延迟加载代理在事务仍处于活动状态时加载。尽管它解决了 LazyInitialization 错误,但延迟加载仍将一一完成,这将导致性能极差。本质上,它只是手动实现了与 FetchType.EAGER 相同的结果(还有更糟糕的方式,因为我们错过了使用 JOIN 和 SUBSELECT 策略的可能性),这甚至与性能的关注相矛盾。

To avoid confusion: Using LAZY fetch type is correct.

为避免混淆:使用 LAZY fetch 类型是正确的。

However, in order to avoid Lazy Loading Exception, in most case, you should have your repository (or DAO?) fetch the required properties.

但是,为了避免延迟加载异常,在大多数情况下,您应该让您的存储库(或 DAO?)获取所需的属性。

The most inefficient way is to do it by accessing the corresponding property and trigger the lazy loading. There are some really big drawbacks:

最低效的方法是通过访问相应的属性并触发延迟加载来实现。有一些非常大的缺点:

  1. Imagine what happen if you need to retrieve multiple level of data.
  2. If the result set is going to be big, then you are issuing n+1 SQLs to DB.
  1. 想象一下,如果您需要检索多级数据会发生什么。
  2. 如果结果集会很大,那么您将向 DB 发出 n+1 条 SQL。

The more proper way is to try to fetch all related data in one query (or a few).

更正确的方法是尝试在一个(或几个)查询中获取所有相关数据。

Just give an example using Spring-data like syntax (should be intuitive enough to port to handcraft Hibernate Repository/DAO):

举个例子,使用类似 Spring-data 的语法(应该足够直观,可以移植到手工 Hibernate Repository/DAO):

interface GroupRepository {
    @Query("from Group")
    List<Group> findAll();

    @Query("from Group g left join fetch g.users")
    List<Group> findAllWithUsers();
}

Join fetching is equally simple in Criteria API (though seems only left join is available), quoted from Hibernate doc:

连接获取在 Criteria API 中同样简单(尽管似乎只有左连接可用),引用自 Hibernate 文档:

List cats = session.createCriteria(Cat.class)
    .add( Restrictions.like("name", "Fritz%") )
    .setFetchMode("mate", FetchMode.EAGER)
    .setFetchMode("kittens", FetchMode.EAGER)
    .list();