避免Hibernate LazyInitializationException的体系结构

问题描述

|| 我正在我的项目的开始。因此,我正在尝试设计一种避免Hibernate LazyInitializationExceptions的体系结构。到目前为止,我的applicationContext.xml具有:
<bean id=\"sessionFactory\" class=\"org.springframework.orm.hibernate3.LocalSessionfactorybean\">
    <property name=\"dataSource\" ref=\"dataSource\"/>
    <property name=\"configLocation\">
        <value>/WEB-INF/hibernate.cfg.xml</value>
    </property>
    <property name=\"configurationClass\">
        <value>org.hibernate.cfg.AnnotationConfiguration</value>
    </property>        
    <property name=\"hibernateProperties\">
        <props>
            <prop key=\"hibernate.dialect\">${hibernate.dialect}</prop>        
            <prop key=\"hibernate.show_sql\">${hibernate.show_sql}</prop>     
        </props>
    </property>
    <property name=\"eventListeners\">
        <map>
            <entry key=\"merge\">
                <bean class=\"org.springframework.orm.hibernate3.support.IdTransferringMergeEventListener\"/>
            </entry>
        </map>
    </property>
</bean>

<bean id=\"dao\" class=\"info.ems.hibernate.HibernateEMSDao\" init-method=\"createSchema\">
    <property name=\"hibernateTemplate\">
        <bean class=\"org.springframework.orm.hibernate3.HibernateTemplate\">
            <property name=\"sessionFactory\" ref=\"sessionFactory\"/>
            <property name=\"flushMode\">
                <bean id=\"org.springframework.orm.hibernate3.HibernateAccessor.FLUSH_COMMIT\" class=\"org.springframework.beans.factory.config.FieldRetrievingfactorybean\"/>                    
            </property>
        </bean>
    </property>        
    <property name=\"schemaHelper\">
        <bean class=\"info.ems.hibernate.SchemaHelper\">                                
            <property name=\"driverClassName\" value=\"${database.driver}\"/>
            <property name=\"url\" value=\"${database.url}\"/>
            <property name=\"username\" value=\"${database.username}\"/>
            <property name=\"password\" value=\"${database.password}\"/>
            <property name=\"hibernateDialect\" value=\"${hibernate.dialect}\"/>   
            <property name=\"dataSourceJndiName\" value=\"${database.datasource.jndiname}\"/>
        </bean>                
    </property>
</bean>       
hibernate.cfg.xml:
<hibernate-configuration>
    <session-factory>       
        <mapping class=\"info.ems.models.User\" />
        <mapping class=\"info.ems.models.Role\" />
    </session-factory>
</hibernate-configuration>
Role.java:
@Entity
@Table(name=\"ROLE\")
@Access(Accesstype.FIELD)
public class Role implements Serializable {

    private static final long serialVersionUID = 3L;

    @Id
    @Column(name=\"ROLE_ID\",updatable=false,nullable=false)
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    private long id;

    @Column(name=\"USERNAME\")
    private String username;

    @Column(name=\"ROLE\")
    private String role;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }
}
和User.java:
@Entity
@Table(name = \"USER\")
@Access(Accesstype.FIELD)
public class User implements UserDetails,Serializable {

    private static final long serialVersionUID = 2L;

    @Id
    @Column(name = \"USER_ID\",nullable=false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @Column(name = \"USERNAME\")
    private String username;

    @Column(name = \"PASSWORD\")
    private String password;

    @Column(name = \"NAME\")
    private String name;

    @Column(name = \"EMAIL\")
    private String email;

    @Column(name = \"LOCKED\")
    private boolean locked;

    @OnetoMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER,targetEntity = Role.class)
    @JoinTable(name = \"USER_ROLE\",joinColumns = { @JoinColumn(name = \"USER_ID\") },inverseJoinColumns = { @JoinColumn(name = \"ROLE_ID\") })
    private Set<Role> roles;

    @Override
    public GrantedAuthority[] getAuthorities() {
        List<GrantedAuthorityImpl> list = new ArrayList<GrantedAuthorityImpl>(0);
        for (Role role : roles) {
            list.add(new GrantedAuthorityImpl(role.getRole()));
        }
        return (GrantedAuthority[]) list.toArray(new GrantedAuthority[list.size()]);
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return !isLocked();
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    @Override
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    @Override
    public String getpassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public boolean isLocked() {
        return locked;
    }

    public void setLocked(boolean locked) {
        this.locked = locked;
    }

    public Set<Role> getRoles() {
        return roles;
    }

    public void setRoles(Set<Role> roles) {
        this.roles = roles;
    }
}
HibernateEMSDao有两种从数据库保存和加载用户方法
public void saveUser(final User user) {     
    getHibernateTemplate().execute(new HibernateCallback() {

        @Override
        public Object doInHibernate(Session session) throws HibernateException,sqlException {
            session.flush();
            session.setCacheMode(CacheMode.IGnorE);
            session.save(user);
            session.flush();
            return null;
        }
    });     
}

public User getUser(final Long id) {
    return (User) getHibernateTemplate().execute(new HibernateCallback() {

        @Override
        public Object doInHibernate(Session session) throws HibernateException,sqlException {
            return session.get(User.class,id);
        }
    });
}
现在我测试是否将I5实施为:
public User getUser(final Long id) {
    getHibernateTemplate().load(User.class,id);        
}
我正在获取LazyInitializationExcaption-会话已关闭。但是第一种方法是正常工作。因此,我需要建议避免在不久的将来出现此异常。任何一小部分信息都是可观的。 谢谢并恭祝安康。 注意:重新启动服务器后出现该错误。 编辑:添加代码
public void saveUser(final User user) {     
    Session session = getSession();
    Transaction transaction = session.beginTransaction();
    session.save(user);
    transaction.commit();
    session.close();
}
public User getUser(final Long id) {
    Session session = getSession();
    session.enableFetchProfile(\"USER-ROLE-PROFILE\");
    User user = (User) session.get(User.class,id);
    session.disableFetchProfile(\"USER-ROLE-PROFILE\");
    session.close();
    return user;
}
    

解决方法

        
saveUser
不应刷新会话。刷新会话确实很少见。让Hibernate处理此事,您的应用程序将更加高​​效。 在这样的地方设置缓存模式也确实很奇怪。你为什么要这么做? 关于为什么在使用
load
而不是
get
时会出现异常的解释:这是因为装入假定您知道该实体存在。它不执行选择查询从数据库中获取用户数据,而是返回一个代理,该代理将在第一次在对象上调用方法时获取数据。如果第一次调用方法时会话已关闭,则Hibernate将无法再获取数据并引发异常。
load
应该很少使用,除非在不获取其数据的情况下启动与现有对象的某种关系。在其他情况下,请使用ѭ10。 我避免LazyInitializationException的一般策略是: 尽可能使用附加对象。 记录通过返回分离对象的方法加载的图,并进行单元测试以确保该图确实已加载 比
upadate
saveOrUpdate
更喜欢
merge
。这些方法可以留下一个对象图,其中某些对象已连接,而另一些对象已分离,这取决于级联。
merge
不会遇到此问题。     ,通常,在与Hibernate,JPA或ORM一起工作时,应对延迟加载一直是一个挑战。 这不仅与防止LazyInitializationException发生有关,而且与有效地执行查询有关。即使在使用常规DAO时,策略也应该只获取您真正需要的数据。 Apress的Mike Keith所著的《ѭ17》一书专门论述了这一部分,但似乎没有一种始终有效的通用解决方案。 有时可以帮助进行FETCH联接。这确实意味着您不使用实体管理器的find方法,而是使用JPQL(如果这是您的毒药,则使用HQL)查询所有内容。您的DAO可以包含一些不同的方法,可以通过这种方法将实体图提高到各个级别。通常,以这种方式相当有效地获取数据,但是在很多情况下,您可能会获取太多数据。 迈克·基思(Mike Keith)提出的另一种解决方案是利用
extended persistence context
。在这种情况下,上下文(休眠会话)未绑定到事务,但保持打开状态。实体因此​​保持连接,并且延迟加载按预期工作。 但是,您必须确保最终关闭扩展上下文。做到这一点的一种方法是让它由绑定到某个范围的有状态会话Bean管理,例如请求范围或对话范围。这样,bean将在此作用域的结尾处被自动销毁,而这又将自动关闭上下文。 然而,这并非没有其自身的问题。开放的上下文将继续消耗内存,并且将其保持打开状态的时间更长(通常是任何比请求范围更长的时间),这可能会带来内存不足的严重风险。如果您知道只与少数几个实体打交道,那是可以的,但是在这里您必须要小心。 依赖于延迟加载的另一个问题是众所周知的1 + N查询问题。遍历中等大小的结果列表可能导致成百上千的查询被发送到数据库。我认为我不必解释这会完全破坏您的表现。 有时可以通过严重依赖第二级缓存来解决此1 + N查询问题。如果实体的数量不是那么大,并且它们的更新频率不是很高,则确保将它们全部缓存(使用Hibernate或JPA的第二级实体缓存)可以大大减少此问题。但是...那是两个很大的“如果”。而且,如果您的主要实体仅引用了一个未缓存的实体,那么您将再次获得数百个查询。 另一种方法是利用Hibernate中的“ 19”支持,该支持可以与其他方法部分组合。参考手册中有一个关于此的部分:http://docs.jboss.org/hibernate/core/3.5/reference/en/html/performance.html#performance-fetching-profiles 因此,对于您的问题似乎并没有一个唯一的肯定答案,而只是很多高度依赖于您的个人情况的想法和实践。