Hibernate有很多对很多 - fetch方法渴望与懒惰

joh*_*ith 9 java orm many-to-many hibernate hibernate-mapping

Hibernate新手.

我有多个用户组关系.三个表:User,Group和UserGroup映射表.

实体:

@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
Run Code Online (Sandbox Code Playgroud)

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

  Criteria criteria = session.createCriteria(Group.class);
  return criteria.list();
Run Code Online (Sandbox Code Playgroud)

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

例如,如果我在用户表中

 username password
 -----------------
 user1     user1
 user2     user2
Run Code Online (Sandbox Code Playgroud)

在组表中

 groupname admin
 ---------------
 grp1      user1
 grp2      user2
Run Code Online (Sandbox Code Playgroud)

在用户组表中

 username groupname
 ------------------
 user1     grp1
 user2     grp2
 user1     grp2
 user2     grp1
Run Code Online (Sandbox Code Playgroud)

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

而不是{grp1,grp2}

如果我将组实体更改为LAZY的获取方法我得到了正确的结果但是hibernate在另一个地方抛出了LazyException ...

请帮助我使用fetch方法以及为什么?

谢谢!

Jam*_*ENL 18

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

如果您确实需要获取组的用户列表,只要您getUsers()在事务会话中进行调用,您就不会得到LazyLoadingException所有新Hibernate用户的祸根.

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

//Service Class
@Transactional
public List<Group> findAll(){
    return groupDao.findAll();
}
Run Code Online (Sandbox Code Playgroud)

以下代码将为您提供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();
}
Run Code Online (Sandbox Code Playgroud)

编辑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;
}
Run Code Online (Sandbox Code Playgroud)

我没有遇到过必须使用上述代码的情况,但它应该相对有效.有关详细信息,Hibernate.initialize()请参阅此处

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

也就是说,如果您发现调用第二种方法远远超过调用第一种方法,请考虑将获取类型更改为EAGER让数据库为您完成工作.


Adr*_*hum 5

尽管 JamesENL 的答案几乎是正确的,但它缺少一些非常关键的方面。

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

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

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

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

  1. 想象一下如果您需要检索多个级别的数据会发生什么。
  2. 如果结果集很大,那么您将向数据库发出 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();
}
Run Code Online (Sandbox Code Playgroud)

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

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