Hibernate could not initialize proxy – no Session – Hibernate无法初始化代理 – 没有会话

最后修改: 2020年 6月 4日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Working with Hibernate, we might have encountered an error that says: org.hibernate.LazyInitializationException : could not initialize proxy – no Session.

在使用Hibernate时,我们可能遇到过这样的错误。org.hibernate.LazyInitializationException : could not initialize proxy – no Session.

In this quick tutorial, we’ll take a closer look at the root cause of the error and learn how to avoid it.

在这个快速教程中,我们将仔细看看这个错误的根本原因,并学习如何避免它。

2. Understanding the Error

2.了解错误

Access to a lazy-loaded object outside of the context of an open Hibernate session will result in this exception.

在一个开放的Hibernate会话的上下文之外访问一个懒加载的对象将导致这个异常。

It’s important to understand what Session, Lazy Initialisation, and Proxy Object are, and how they come together in the Hibernate framework:

了解什么是SessionLazy Initialisation,Proxy Object,以及它们在Hibernate框架中是如何结合的,这一点很重要。

  • Session is a persistence context that represents a conversation between an application and the database.
  • Lazy Loading means that the object won’t be loaded to the Session context until it is accessed in code.
  • Hibernate creates a dynamic Proxy Object subclass that will hit the database only when we first use the object.

This error occurs when we try to fetch a lazy-loaded object from the database by using a proxy object, but the Hibernate session is already closed.

当我们试图通过使用代理对象从数据库中获取一个懒惰加载的对象,但Hibernate会话已经关闭时,会发生这个错误。

3. Example for LazyInitializationException

3.LazyInitializationException的例子

Let’s see the exception in a concrete scenario.

让我们在一个具体的场景中看看这个例外。

We want to create a simple User object with associated roles. We’ll use JUnit to demonstrate the LazyInitializationException error.

我们想创建一个简单的User对象,并与之相关联的角色。我们将使用JUnit来演示LazyInitializationException错误。

3.1. Hibernate Utility Class

3.1.Hibernate实用类

First, let’s define a HibernateUtil class to create a SessionFactory with configuration.

首先,让我们定义一个HibernateUtil类来创建一个带有配置的SessionFactory

We’ll use the in-memory HSQLDB database.

我们将使用内存中的HSQLDB数据库。

3.2. Entities

3.2 实体

Here’s our User entity:

这里是我们的User实体。

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

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private int id;

    @Column(name = "first_name")
    private String firstName;
    
    @Column(name = "last_name")
    private String lastName;
    
    @OneToMany
    private Set<Role> roles;
    
}

And the associated Role entity:

以及相关的Role实体。

@Entity
@Table(name = "role")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private int id;

    @Column(name = "role_name")
    private String roleName;
}

As we can see, there’s a one-to-many relationship between User and Role.

我们可以看到,UserRole之间存在一对多的关系。

3.3. Creating User With Roles

3.3.创建有角色的用户

Then we’ll create two Role objects:

然后我们将创建两个Role对象。

Role admin = new Role("Admin");
Role dba = new Role("DBA");

We’ll also create a User with the roles:

我们还将创建一个带有角色的User

User user = new User("Bob", "Smith");
user.addRole(admin);
user.addRole(dba);

Finally, we can open a session and persist the objects:

最后,我们可以打开一个会话并持久化这些对象。

Session session = sessionFactory.openSession();
session.beginTransaction();
user.getRoles().forEach(role -> session.save(role));
session.save(user);
session.getTransaction().commit();
session.close();

3.4. Fetching Roles

3.4.获取角色

In this first scenario, we’ll see how to fetch user roles in a proper way:

在这第一个场景中,我们将看到如何以适当的方式获取用户角色。

@Test
public void whenAccessUserRolesInsideSession_thenSuccess() {

    User detachedUser = createUserWithRoles();

    Session session = sessionFactory.openSession();
    session.beginTransaction();
		
    User persistentUser = session.find(User.class, detachedUser.getId());
		
    Assert.assertEquals(2, persistentUser.getRoles().size());
		
    session.getTransaction().commit();
    session.close();
}

Here we access the object inside the session, and therefore, there’s no error.

在这里,我们在会话中访问对象,因此,没有错误。

3.5. Fetching Roles Failure

3.5.获取角色失败

In this second scenario, we’ll call a getRoles method outside the session:

在第二种情况下,我们将在会话外调用一个getRoles方法。

@Test
public void whenAccessUserRolesOutsideSession_thenThrownException() {
		
    User detachedUser = createUserWithRoles();

    Session session = sessionFactory.openSession();
    session.beginTransaction();
		
    User persistentUser = session.find(User.class, detachedUser.getId());
		
    session.getTransaction().commit();
    session.close();

    thrown.expect(LazyInitializationException.class);
    System.out.println(persistentUser.getRoles().size());
}

In this case, we try to access the roles after the session is closed, and as a result, the code throws a LazyInitializationException.

在这种情况下,我们试图在会话关闭后访问角色,结果,代码抛出一个LazyInitializationException.

4. How to Avoid the Error

4.如何避免错误

Now let’s take a look at four different solutions to overcome the error.

现在让我们来看看四种不同的解决方案来克服这个错误。

4.1. Open Session in Upper Layer

4.1 在上层打开会话

The best practice is to open a session in the persistence layer, using the DAO Pattern, for example.

最好的做法是在持久化层中打开一个会话,例如使用DAO模式

We can open the session in the upper layers to access the associated objects in a safe manner. For instance, we can open the session in the View layer.

我们可以在上层打开会话,以安全的方式访问相关对象。例如,我们可以在View层中打开会话。

As a result, we’ll see an increase in response time, which will affect the performance of the application.

因此,我们将看到响应时间的增加,这将影响应用程序的性能。

This solution is an anti-pattern in terms of the Separation of Concerns principle. In addition, it can cause data integrity violations and long-running transactions.

就关注点分离原则而言,这种解决方案是一种反模式。此外,它还可能导致违反数据完整性和长期运行的事务。

4.2. Turning on enable_lazy_load_no_trans Property

4.2.打开enable_lazy_load_no_trans属性

This Hibernate property is used to declare a global policy for lazy-loaded object fetching.

这个Hibernate属性被用来声明一个全局策略,用于懒惰加载对象的获取。

By default, this property is false. Turning it on means that each access to an associated lazy-loaded entity will be wrapped in a new session running in a new transaction:

默认情况下,这个属性是false。打开它意味着对相关的懒加载实体的每次访问都将被包裹在一个新的事务中运行的新会话中。

<property name="hibernate.enable_lazy_load_no_trans" value="true"/>

Using this property to avoid the LazyInitializationException error isn’t recommended, since it’ll slow down the performance of our application. This is because we’ll end up with an n + 1 problem. Simply put, it means one SELECT for the User and N additional SELECTs to fetch the roles of each user.

使用这个属性来避免LazyInitializationException 错误并不值得推荐,因为它会降低我们应用程序的性能。这是因为我们会最终出现n + 1的问题简单地说,这意味着为User进行一次SELECT,以及另外N次SELECT来获取每个用户的角色。

This approach isn’t efficient and is also considered an anti-pattern.

这种方法并不高效,也被认为是一种反模式。

4.3. Using  FetchType.EAGER Strategy

4.3.使用 FetchType.EAGER 策略

We can use this strategy along with the @OneToMany annotation:

我们可以把这个策略和@OneToMany注解一起使用。

@OneToMany(fetch = FetchType.EAGER)
@JoinColumn(name = "user_id")
private Set<Role> roles;

This is a kind of compromised solution for a particular usage when we need to fetch the associated collection for most of our use cases.

当我们在大多数用例中需要获取相关的集合时,这是一种针对特定用法的折中方案。

It’s much easier to declare the EAGER fetch type, instead of explicitly fetching the collection for most of the different business flows.

声明EAGER 获取类型要容易得多,而不是为大多数不同的业务流明确获取集合。

4.4. Using Join Fetching

4.4.使用Join Fetching

We can also use a JOIN FETCH directive in JPQL to fetch the associated collection on-demand:

我们也可以在JPQL中使用JOIN FETCH指令来按需获取相关集合。

SELECT u FROM User u JOIN FETCH u.roles

Or we can use the Hibernate Criteria API:

或者我们可以使用Hibernate Criteria API。

Criteria criteria = session.createCriteria(User.class);
criteria.setFetchMode("roles", FetchMode.EAGER);

Here, we specify the associated collection that should be fetched from the database, along with the User object on the same round trip. Using this query improves the efficiency of iteration, since it eliminates the need for retrieving the associated objects separately.

在这里,我们指定了应该从数据库中获取的相关集合,以及在同一往返中获取的User对象。使用这个查询提高了迭代的效率,因为它消除了单独检索相关对象的需要。

This is the most efficient and fine-grained solution to avoid the LazyInitializationException error.

这是避免LazyInitializationException 错误的最有效和最细化的解决方案。

5. Conclusion

5.总结

In this article, we learned how to deal with the org.hibernate.LazyInitializationException : could not initialize proxy – no Session error.

在这篇文章中,我们学习了如何处理org.hibernate.LazyInitializationException : could not initialize proxy – no Session error.

We explored different approaches, along with performance issues. We also discussed the importance of using a simple and efficient solution in order to avoid affecting performance.

我们探讨了不同的方法,以及性能问题。我们还讨论了使用简单有效的解决方案的重要性,以避免影响性能。

Finally, we demonstrated how the join-fetching approach is a good way to avoid the error.

最后,我们演示了连接获取的方法是如何避免错误的。

As always, the code is available over on GitHub.

像往常一样,代码可在GitHub上获得