通过Spring管理hibernate session来解决lazy-load的问题
学习Spring的事务还要源于我在开发中遇到的困难。我的网站Server端是基于S2SH的。问题是这样的:
在Service层我有一个service,部分代码如下:
@Service("userService")
@Transactional
public class UserServiceImpl implements UserService {
private static String getUserByNameHQL = "from User as user where user.userName = ?";
private static String deleteByIDHQL = "delete from User as user where user.id=?";
private static String findLogonInfoHQL = "from LogonValidationInfo as info where info.userName=? and info.sessionid=?";
private static String deleteLogonInfoHQL = "delete from LogonValidationInfo as info where info.userName=? and info.sessionid=?";
private HibernateDaoSupport dao;
public HibernateDaoSupport getDao() {
return dao;
}
@Autowired
public void setDao(HibernateDaoSupport dao) {
this.dao = dao;
}
public User getUserByName(String userName ) {
User user = null;
List results = this.dao.getHibernateTemplate().find(getUserByNameHQL,
userName);
if (results != null && !results.isEmpty()) {
user = (User) results.get(0);
}
return user;
}
Dao的继承的Spring提供的HibernateDaoSupport,并通过Spring标注注入到UserService里,dao的代码如下
@Repository
public class HibernateGenericDaoImpl extends HibernateDaoSupport{
@Autowired
public void setSessionFactoryOverride(SessionFactory sessionFactory)
{
super.setSessionFactory(sessionFactory);
}
}
实体类User, Node存在一对多的双向关联关系,实体类比较通俗就不贴出来了,hbm文件如下,User对象通过自己的Set<TimelineNode>映射了一组Node类型。
<class name="User" table="user">
<id name="id" column="uid">
<generator class="increment" />
</id>
<property name="firstName" column="firstname"/>
<property name="lastName" column="lastname"/>
<property name="userPwd" column="password"/>
<property name="userName" column="username"/>
<set name="nodes" inverse="true" cascade="all" lazy="true"
table="timelinenode">
<key column="user_id" not-null="true"></key>
<one-to-many class="TimeLineNode"/>
</set>
</class>
<class name="TimeLineNode" table="timelinenode"> <id name="ID" column="id"> <generator class="increment" /> </id> <property name="startDate" type="date"> <column name="starttime" sql-type="datetime" /> </property> <property name="endDate" type="date"> <column name="endtime" sql-type="datetime" /> </property> <property name="headline" type="text" column="header"> </property> <property name="text" type="text" column="article"> <property name="isStartNode" column="titleSlide" /> <property name="tags" /> <property name="media" /> <property name="credit" /> <property name="caption" /> <property name="bgrImg" column="bgrimg" /> <many-to-one name="author" class="User" lazy="false" column="user_id" cascade="all"> </many-to-one> </class>
调用过程是UserAction->UserService->Dao
UserAction的入口方法:</property>public String login() {
...............
User user = null;
try {
user = userService.getUserByName(uname);
Set nodes=user.getNodes(); //lazy load ,sesseion closed exception will throw out here
..................
}
红字的那一行,因为在hibernate映射文件里采用了lazy="true"的加载方法,user.getNodes()的时候会从新去数据库中得数据,可是这个时候user对象所处的session已经关闭掉了。会抛出一个异常
org.hibernate.LazyInitializationException: could not initialize proxy - no Session.
解决这个问题有如下几个方案:
一.设置第一个映射文件中lazy="false",采用非延迟加载,不过这样做挺扯淡,我本来就为了系统性能提升才采用的延迟加载策略,这么一改我不就白干了,所以果断否掉,这条也作为解决方案的其中一个的原因则是如果系统性能要求没那么高或者一次性加载的东西并不太占用内存,可以考虑采用这样的方式。
二.Spring提供的方案OpenSessionInView.
Spring提供了OpenSessionInViewInterceptor和OpenSessionInViewFilter这两种方式实现这个解决方案。
这两个主要都是在一个url请求到来的时候为程序打开一个hibernate session,然后请求结束的时候关闭这个session,我没有采用这种解决方案,因为
1.Open Session In View的解决方案存在性能上的争论。
2.我的lazy load发生在action层(这里要讲一下,其实执行lazy load的这段代码完全可以放到service层,只不过这个逻业务逻辑不是很复杂,加上本人手懒,就没有抽到service层里,如果要加入事务的话,最好还是要先抽一下这块代码),不应该讲其范围扩大到整个request范围,还要涉及到request的跳转问题(这个我也不是很确定,不过redirect的话该session应该是失效的,未经测试纯属猜测)。
三.采用hibernate的API手动解析目标代理。通过查阅Hibernate的API,发现了下面这个东东:
Hibernate的静态方法isInitialized
public static boolean isInitialized(Object proxy)
描述如下:
Check if the proxy or persistent collection is initialized.
- Parameters:
-
proxy
- a persistable object, proxy, persistent collection or null - Returns:
- true if the argument is already initialized, or is not a proxy or collection
OK,在我这个例子里,参数proxy代表的即是user.getNodes();
修改了一下userService.getUserByName方法:
public User getUserByName(String userName ,boolean initChildren) {
User user = null;
List results = this.dao.getHibernateTemplate().find(getUserByNameHQL,
userName);
if (results != null && !results.isEmpty()) {
user = (User) results.get(0);
}
if(user!=null && initChildren){
initializeObject(user.getNodes());
}
return user;
}
initializeObject方法如下:
public void initializeObject(Object proxy) {
Hibernate.initialize(proxy); //To solve the lazy-load problem
}
测试,搞定。这个困扰了我几天的问题终于解决了。