问题描述
||
我正在我的项目的开始。因此,我正在尝试设计一种避免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
因此,对于您的问题似乎并没有一个唯一的肯定答案,而只是很多高度依赖于您的个人情况的想法和实践。