要在spring中使用hibernate的延迟加载,我们先要模拟一个延迟加载的场景。
hibernate的get和load的区别相信大家都知道,load是通过代理加载实体,如果只访问id是不会读库将所有属性加载进来的,这个就是一个延迟加载的简单场景。
一、如果按照标准的spring+hibernate的整合,当使用load加载一个实体是,会报no session的错误
这是因为,延迟加载需要在同一个session中,如果按照标签配置,session在load后就已经关闭,因此页面上显示实体属性时通过代理延迟加载便会报no session的错误了。
解决办法,spring中提供了一个OpenSessionInViewFilter的filter用来在一个http请求中保持hibernate的session,配置如下:
<filter>
<filter-name>hibernateLazyFilter</filter-name>
<filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>hibernateLazyFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
到此,就能解决延迟加载的问题吗?请往下看
二、此时查询数据库均没问题,但是当update一个实体,会报Write operations are not allowed in read-only mode (FlushMode.NEVER) - turn your Session into 错误
这是因为,使用了OpenSessionInViewFilter后,spring会把hibernate的FlushMode改为FlushMode.NEVER,FlushMode.NEVER是不允许更新修改数据库操作的。
解决办法:
1.使用事务控制修改FlushMode
<property name="transactionAttributes">
<props>
<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="save*">PROPAGATION_REQUIRED</prop>
<prop key="add*">PROPAGATION_REQUIRED</prop>
<prop key="update*">PROPAGATION_REQUIRED</prop>
<prop key="remove*">PROPAGATION_REQUIRED</prop>
</props>
</property>
这里不具体解释了,声明式事务的概念说起来也很多,需要注意的是HibernatTemplate的flushMode要设置为commit。
2.修改OpenSessionInViewFilter的属性,改为AUTO
<filter>
<filter-name>hibernateLazyFilter</filter-name>
<filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
<init-param>
<param-name>flushMode</param-name>
<param-value>AUTO</param-value>
</init-param>
</filter>
这里我们使用的是第二种方式。
三、到此查询修改操作不会再报错了,是不是已经配置好了呢?仔细一看,修改是没报错了,但是修改的结果并没有写入数据库
这是因为,spring在flush时会判断一下,请详见spring的源代码:
HibernateTemplate.doExecute{
......
flushIfNecessary(session, existingTransaction);
......
}
protected void flushIfNecessary(Session session, boolean existingTransaction) throws HibernateException {
if (getFlushMode() == FLUSH_EAGER || (!existingTransaction && getFlushMode() != FLUSH_NEVER)) {
logger.debug("Eagerly flushing Hibernate session");
session.flush();
}
}
意思就是,spring的flushmode为FLUSH_EAGER || (flushmode不是FLUSH_NEVER && 不是事务)
很遗憾使,用了OpenSessionInViewFilter后,existingTransaction=true,也就是说此时spring认为是一个事务
解决办法:
1.将HibernateTemplate的flushmode设置为FLUSH_EAGER
2.既然spring不能自动flush,咱们就自己来吧,在update()后flush()
到此,延迟加载已经可以正常使用了。
附:在调试是还遇到过这样一个错误:
a different object with the same identifier value was already associated with the session。
这是因为在session的生命周期中如果存在多个实体,hibernate不知道以哪个为准,可以用evict、clear、merg解决
@NotFound(action=NotFoundAction.IGNORE)
many-to-one加这个注释延时加载就失效