Hibernate多对多关联 - 抓取方法eager vs lazy

12

刚接触 Hibernate。

我有一个用户组多对多的关系。 三个表:用户,组和用户组映射表。

实体:

New to Hibernate.

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

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

请注意,在Group Entity中,我使用了EAGER方法进行fetch操作。现在,当我调用我的DAO来检索系统中的所有组时,使用以下标准:

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

我正在从映射表(usergroup)中获取所有行,而不是获取实际组数...

例如,如果我的用户表中有

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

在群组表中

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

在用户组表中

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

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

而不是{grp1,grp2}

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

请帮忙决定应该使用哪种fetch方法以及原因?

谢谢!

2个回答

23

懒惰的人会告诉你始终使用FetchType.EAGER,这是有违直觉的。这些人通常不关心数据库性能,只关心让他们的开发生活更轻松。我要说的是,为了获得增加的性能效益,应该使用FetchType.LAZY。因为数据库访问通常是大多数应用程序的性能瓶颈,每一个小改进都有所帮助。

如果您确实需要获取某个组的用户列表,只要在事务性会话中调用getUsers()方法,就不会出现所有新Hibernate用户的噩梦LazyLoadingException

以下代码将获取所有组,而不会填充这些组的用户列表。

//Service Class
@Transactional
public List<Group> findAll(){
    return groupDao.findAll();
}
以下代码将在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();
}

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

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

如果您不想编写不同的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;
}

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

我将此操作放在服务层中而不是在DAO中获取它们,因为这样你只需要在服务中创建一个新方法,而不必再创建一个单独的DAO方法。重要的是你已经将getUsers()调用封装在了事务中,这样Hibernate就可以使用创建的会话来运行附加查询。这也可以通过在DAO中编写集合的联接条件来完成,但我自己从来没有这样做过。

即便如此,如果你发现你调用第二个方法比第一个方法频繁得多,考虑将你的提取类型更改为EAGER并让数据库为你处理工作。


非常感谢您提供如此详细的答案!!您帮了我很多 :) - john Smith
1
非常好的解释。Eager 应该只在整个应用程序中预期并真正需要时使用。 - Charles Morin

5
虽然JamesENL的答案几乎是正确的,但它缺少一些非常关键的方面。他所做的就是在事务仍然活跃时强制懒加载代理进行加载。虽然它解决了LazyInitialization错误,但懒加载仍将逐个进行,这将导致极差的性能。本质上,它只是手动实现FetchType.EAGER相同的结果(甚至更糟,因为我们错过了使用JOIN和SUBSELECT策略的可能性),这甚至与性能有关的问题相矛盾。为了避免混淆:使用LAZY fetch type是正确的。然而,在大多数情况下,为了避免懒加载异常,您应该让存储库(或DAO)获取所需的属性。最低效的方法是通过访问对应的属性并触发懒加载来完成。存在一些非常大的缺点: 1. 想象一下如果你需要检索多层数据会发生什么。 2. 如果结果集很大,则要向DB发出n+1个SQL。
更合适的方法是试图在一个查询(或几个查询)中获取所有相关的数据。只举一个使用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();
}

在Criteria API中,联合抓取同样简单(虽然似乎只有左联接可用)。引用Hibernate文档:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接