【问题标题】:Hibernate lazy initialization - failed to lazily initialize a collectionHibernate 延迟初始化 - 无法延迟初始化集合
【发布时间】:2014-01-19 17:38:30
【问题描述】:

每当我尝试从我的用户表中检索数据时,我都会收到以下错误,我已经在网上搜索了很多试图找出可能出现的问题但找不到任何东西,所以有人可以通过告诉我来帮助我我错过了什么/我错了?

错误:

错误:org.hibernate.LazyInitializationException - 懒惰失败 初始化角色集合:com.domain.crm.domain.Role.users, no 会话或会话已关闭 org.hibernate.LazyInitializationException:延迟初始化失败 角色集合:com.domain.crm.domain.Role.users,无会话或 会话已关闭

用户类别:

@Entity
@Table(name="COM_USER")
public class User {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Column(name="USER_ID")
private Long id;

@Column(name="USER_NAME",nullable=false,length=25,unique=true)
private String userName;

@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name="ROLE_ID",nullable=false)
private Role role;
}

角色类:

@Entity
@Table(name="COM_ROLE")
public class Role {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Column(name="ROLE_ID")
private Long id;

@Column(name="ROLE",nullable=false,unique=false)
private Integer Role;

@OneToMany(mappedBy="role")
private Set<User> users=new HashSet<User>();
}   

用户DAO类方法被调用以收集所有用户:

public List<User> getUsers(Long page, Long pageSize) {
    Long start = (page-1)*pageSize;     
    return sessionfactory.getCurrentSession().createQuery("from User u ").setFirstResult(start.intValue()).setMaxResults(pageSize.intValue()).list();
}

用户服务类方法:

@Transactional
public List<User> getUsers(Long page, Long pageSize) {
    return userdao.getUsers(page, pageSize);
}

控制器类调用方法:

@RequestMapping(value = "/users/list-user-data")
@ResponseBody
public UserListData listUserData(HttpServletRequest request, HttpServletResponse response, Model model) throws Exception {

    UserListData listData = new UserListData();

    String page = request.getParameter("page");
    Long pageLong = Long.parseLong(page);
    Long pageSize = (long)15;

    List<User> searchResults = iuserservice.getUsers(pageLong, pageSize);

    if( searchResults != null ){
        List<List<Object>> aaData = new ArrayList<List<Object>>();
        List<Object> listItem = null;

        for( User u : searchResults ){
            listItem = new ArrayList<Object>();
            listItem.add(u.getLastName());
            listItem.add(u.getFirstName());
            listItem.add(u.getUserName());
            listItem.add(u.getEmail());
            listItem.add(u.getRole());

            aaData.add(listItem);
        }
        listData.setAaData(aaData);
    }



    int totalCount = iuserservice.getAllUsersCount().intValue();

    System.out.println("Number of records in DB:  "+totalCount);
    listData.setiTotalRecords(totalCount);

    return listData;
}

最后是我的 pom.xml 依赖:

<properties>
    <java-version>1.6</java-version>
    <org.springframework-version>3.1.0.RELEASE</org.springframework-version>
    <org.aspectj-version>1.6.9</org.aspectj-version>
    <org.slf4j-version>1.5.10</org.slf4j-version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${org.springframework-version}</version>
        <exclusions>
            <!-- Exclude Commons Logging in favor of SLF4j -->
            <exclusion>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
             </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>${org.springframework-version}</version>
    </dependency>
    <!-- AspectJ -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>${org.aspectj-version}</version>
    </dependency>   
    <!-- Logging -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>${org.slf4j-version}</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
        <version>${org.slf4j-version}</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>${org.slf4j-version}</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.15</version>
        <exclusions>
            <exclusion>
                <groupId>javax.mail</groupId>
                <artifactId>mail</artifactId>
            </exclusion>
            <exclusion>
                <groupId>javax.jms</groupId>
                <artifactId>jms</artifactId>
            </exclusion>
            <exclusion>
                <groupId>com.sun.jdmk</groupId>
                <artifactId>jmxtools</artifactId>
            </exclusion>
            <exclusion>
                <groupId>com.sun.jmx</groupId>
                <artifactId>jmxri</artifactId>
            </exclusion>
        </exclusions>
        <scope>runtime</scope>
    </dependency>

    <!-- @Inject -->
    <dependency>
        <groupId>javax.inject</groupId>
        <artifactId>javax.inject</artifactId>
        <version>1</version>
    </dependency>

    <!-- Servlet -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>servlet-api</artifactId>
        <version>2.5</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>jsp-api</artifactId>
        <version>2.1</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
        <version>1.2</version>
    </dependency>
    <!-- Test -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.7</version>
        <scope>test</scope>
    </dependency>        
    <!-- Spring Security -->
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-core</artifactId>
        <version>3.1.3.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-web</artifactId>
        <version>3.1.3.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-config</artifactId>
        <version>3.1.3.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-taglibs</artifactId>
        <version>3.1.3.RELEASE</version>
        <type>jar</type>
        <scope>compile</scope>
    </dependency>       
    <!-- Mysql -->  
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.6</version>
    </dependency>           
    <!-- Hibernate -->
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <version>3.6.0.Final</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-orm</artifactId>
        <version>${org.springframework-version}</version>
    </dependency>       
    <!-- Commons DBCP -->   
    <dependency>
        <groupId>commons-dbcp</groupId>
        <artifactId>commons-dbcp</artifactId>
        <version>1.2.2</version>
    </dependency>   
    <dependency>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-resources-plugin</artifactId>
        <version>2.4.3</version>
    </dependency>   
    <dependency>
        <groupId>net.sf.jasperreports</groupId>
        <artifactId>jasperreports</artifactId>
        <version>5.2.0</version>
    </dependency>    
    <dependency>
        <groupId>commons-collections</groupId>
        <artifactId>commons-collections</artifactId>
        <version>3.1</version>
    </dependency>           
</dependencies>

感谢您的宝贵时间

【问题讨论】:

  • 错误说明了一切。您从数据库加载对象,然后关闭会话。然后,您的对象处于分离状态。然后,您尝试访问一个惰性集合。您需要扩展会话的范围 - 在视图中打开会话是一个很好的模式。或者您可以急切地加载集合。
  • @BoristheSpider 请花点时间检查一下 User 类,我已经迫不及待地加载了集合
  • @BoristheSpider 我也没有关闭会话,当我在控制器方法的中间时出现错误,甚至在它完成过程之前。谢谢
  • @MChan:不,您不会急切地加载用户。 OneToMany 关联默认是惰性的。是的,一旦事务结束,会话就会关闭,即在事务性UserService.getUsers() 方法返回之后。
  • 您应该在服务方法中进行处理。并且应该从服务方法返回UserListData

标签: java spring hibernate


【解决方案1】:

我在这里发布示例。基本想法是只拥有 3 个数据库表,其中 2 个用于实体 UserRole,最后一个作为映射表,例如 user_role。在用户表中,我们存储用户详细信息,在角色表中,我们存储角色 ID 和名称,在映射表中,我们将用户映射到角色。我从我自己的项目中复制。我的类扩展的 BaseEntity 只是一个映射的超类,它具有每个实体的通用字段,例如 id、创建/修改日期等。

用户实体

@Entity
@Table(name = "user_t")
public class User extends BaseEntity {

    @Column(name = "username", nullable = false, unique = true)
    private String userName;

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

    @ManyToMany
    @JoinTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id"))
    private Set<Role> role = new HashSet<Role>();

    // getters & setters
}

角色实体

@Entity(name = "role_t")
public class Role extends BaseEntity {

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

    public Role() {
    }

    public Role(String roleName) {
        this.roleName = roleName;
    }

    // getters & setters
}

注意ManyToMany 默认使用eagerly fetch 类型,所以我们不需要设置它。现在,当我从我的服务中查询我的User 实体时。 role 集合不会被获取。如果我需要获取属于该用户的角色,那么我需要使用查询手动获取它们。我使用 Spring Data JPA 存储库,例如,对于单个结果,查询看起来像这样。

UserRepository 类

public interface UserRepository extends JpaRepository<User, Long> {

    @Query("SELECT u FROM User u JOIN FETCH u.role WHERE u.userName = (:userName)")
    public User findByUserNameAndFetchRoles(@Param("userName") String userName);

    @Query("FROM User u JOIN FETCH u.role")
    public List<User> getAllUsersAndFetchRoles(); // **query that you would use!**
}

然后在您的UserService 层中,您将使用该存储库/DAO 查询。对我来说,对角色使用热切获取是必要的,因为每次我想查询用户列表时,我都不需要获取角色,因为在我的真实项目中,角色也有一组权限和只是不值得查询你不需要的东西。

【讨论】:

    【解决方案2】:

    问题是@OneToMany 关联默认是惰性的,当您调用getUsers() 时,会话已经在您的服务方法中关闭。 解决它的一种方法是急切地加载子记录。 但要小心,因为这可能会导致您加载太多数据,这会减慢速度并耗尽内存。例如,在您的 Role 实体中,如果您将其设置为急切加载 Users

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

    每当您加载 Role 时,Hibernate 都会加载其所有子 Users

    处理您的情况的一种方法是在 @Transactional 方法内对服务进行所有工作,这应确保会话仍然打开(最好使用服务方法 @Transactional 而不是 DAO)

    @Transactional
    public UserListData getUserListData(Long page, Long pageSize) {
        List<User> searchResults = userdao.getUsers(page, pageSize);
        UserListData listData = new UserListData();
        if( searchResults != null ){
           List<List<Object>> aaData = new ArrayList<List<Object>>();
           List<Object> listItem = null;
    
            for( User u : searchResults ){
                listItem = new ArrayList<Object>();
                listItem.add(u.getLastName());
                listItem.add(u.getFirstName());
                listItem.add(u.getUserName());
                listItem.add(u.getEmail());
                listItem.add(u.getRole());
    
                aaData.add(listItem);
            }
            listData.setAaData(aaData);
        }
     return listData;
    }
    

    【讨论】:

    • 我已经按照你刚才的建议移动了服务类中的所有代码,但我仍然遇到同样的错误,我也将 fetchtype 设置为 eager
    • 这行不通。如果您在用户实体上调用 getRole(),则角色集合仍未初始化。
    【解决方案3】:

    您正在访问Role 实体中users 集合字段的某个方法(在上面的代码中未显示)。

    【讨论】:

      【解决方案4】:

      您不能从具有急切获取类型的同一事务中加载 Roles 集合。要解决这个问题,您需要手动初始化 Roles 集合或在查询中执行 fetch join。这应该会为您解决问题。

      public List<User> getUsers(Long page, Long pageSize) {
          Long start = (page-1)*pageSize;     
          return sessionfactory.getCurrentSession().createQuery("FROM User u JOIN FETCH u.role ").setFirstResult(start.intValue()).setMaxResults(pageSize.intValue()).list();
      }
      

      我还建议在UserRoles 之间建立ManyToMany 关系并使用映射表。使用以下内容并从角色实体中删除用户集。在角色表上重复角色记录是多余的。

      @ManyToMany
      @JoinTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id"))
      private Set<Role> role = new HashSet<Role>();
      

      【讨论】:

      • 谢谢你的回复,但是为什么Users和Roles之间是多对多的,一个User只能有一个角色,而一个角色可以属于很多用户,所以是一对多的关系,除非我在这里遗漏了一些东西,所以你能解释一下为什么多对多吗?
      • 基于角色的访问控制最佳实践不是将角色分配给用户,而是将用户分配给角色。您必须更广泛地考虑您的数据库设计。如果您打算扩展它,那么您将被困在您的解决方案中。使用 ManyToMany,您可以创建角色,并通过映射表将用户映射到角色。如果您计划添加基于权限/权限的身份验证,这允许您扩展您的解决方案。
      • 据我所知,ROLE 是相当静态的,这意味着我不能动态构建新的 ROLE_,因此必须首先在代码中定义所有 ROLE(例如:ROLE_ADMIN、ROLE_CLIENT...等)然后为每个保存在 ROLE 表中的 ROLE_ 分配一个 ID。如果我在用户和角色之间建立了多对多关系,这意味着一个用户可以同时拥有多个角色,到目前为止我是否正确地关注了你?好的,现在就可扩展性而言,我已经受限于静态角色,所以它不是可扩展的……有意义吗?
      • 不,角色不是静态的,您必须阅读 Spring Security 中的实际角色。这对他们来说是一个广泛的误解,而且相当令人困惑。您在应用程序中分配的角色只是一个必须以 ROLE_ 前缀开头的名称,这实际上不是角色,而是实际上是权限。在扩展下,我的意思是这样的例子:slackspace.de/articles/roles-permissions-with-spring-security-3
      • 使用 JOIN FETCH 后同样的问题,我仍然得到错误 ERROR: org.hibernate.LazyInitializationException - failed to lazily initialize a collection of role: com.domain.crm.domain.Role.users, no session或会话已关闭 org.hibernate.LazyInitializationException:未能延迟初始化角色集合:com.domain.crm.domain.Role.users,没有关闭会话或会话
      猜你喜欢
      • 2014-12-19
      • 1970-01-01
      • 2020-05-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多