架构,以避免Hibernate LazyInitializationExceptions
我在我的项目开始。所以我试图设计一个避免Hibernate LazyInitializationExceptions的架构。到目前为止,我的applicationContext.xml有:架构,以避免Hibernate LazyInitializationExceptions
<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", updatable=false, 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有两种保存方法和lo从数据库ading用户:
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);
}
});
}
现在我测试了一下,如果我实现HibernateEMSDao#getUser
为:
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
应该很少使用,除非启动与现有对象的某种关系而不必获取其数据。在其他情况下使用get
。
我避免LazyInitializationException中一般的策略是:
- 使用附加的对象只要有可能。该图由方法加载
- 返回文档分离对象,和单元测试,该图被确实装入
- 喜欢
merge
超过upadate
和saveOrUpdate
。这些方法可以留下附有一些对象的对象的图形,而其他对象则根据级联而分离。merge
不会遇到这个问题。
与延迟加载处理是持续的挑战与Hibernate,JPA或奥姆斯在一般工作时。
这不仅是为了防止发生LazyInitializationException,而且还关于高效地执行查询。即使使用通用的DAO,策略也应该尽可能多地获取您真正需要的数据。
来自Mike Keith by Apress的书Pro JPA 2
专门介绍了这方面的内容,但似乎并没有一个通用的解决方案总是有效。
有时它可以帮助做FETCH连接。这确实意味着你不使用实体管理器的find方法,但是使用JPQL(或HQL,如果这是你的毒药)查询所有内容。您的DAO可以包含几种不同的方法,以这种方式将实体图形提升到各种级别。数据通常以这种方式相当高效地获取,但是在很多情况下,您可能会获取太多数据。
Mike Keith建议的另一种解决方案是利用extended persistence context
。在这种情况下,上下文(Hibernate会话)未绑定到事务,但保持打开状态。因此实体保持连接状态,延迟加载按预期工作。
尽管你必须确保最终关闭扩展的上下文。这样做的一种方式是由一个有约束的有状态会话bean来管理,例如请求范围或对话范围。这样,这个bean就会在这个范围的末尾自动销毁,并且这个轮到它自动关闭上下文。然而,它并不是没有它自己的问题。一个开放的上下文将继续消耗内存并使其保持更长时间(通常比请求范围更长)可能会导致内存不足的严重风险。如果你知道你只与少数实体打交道没关系,但你必须在这里小心。
取决于延迟加载的另一个问题是众所周知的1 + N查询问题。遍历中等大小的结果列表可能会导致数百或数千个查询被发送到数据库。我想我不必解释这可以完全破坏你的表现。
这个1 + N查询问题有时可以通过大量依赖二级缓存来解决。如果实体数量不是那么大,并且如果它们没有经常更新,确保它们全部被缓存(使用Hibernate或JPA的二级实体缓存)可以大大减少这个问题。但是......这是两个大的“如果”。如果您的主要实体只引用一个未缓存的实体,您将再次获得数百个查询。
另一种方法是利用Hibernate中的fetch profile
支持,该支持可以部分与其他方法结合使用。参考手册在此处有一节:http://docs.jboss.org/hibernate/core/3.5/reference/en/html/performance.html#performance-fetching-profiles
因此,对于您的问题似乎没有单一明确的答案,但只有很多想法和实践都高度依赖于您的个人情况。
“让Hibernate照顾好这一点,你的应用程序将会更有效率。”我是否应该为此做特殊设置? – 2011-05-29 15:28:49
我在互联网上找到了一个例子,我看到这种类型的操作像第一个session.flush(),然后是缓存模式操作,最后再次flush()。 – 2011-05-29 15:30:48
不要相信你在互联网上找到的所有东西。阅读参考手册:http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html_single/。当需要刷新时,默认的刷新模式会自动刷新。 – 2011-05-29 17:04:05