Web项目中定时任务无法绑定SessionFactory的问题解决
正常我们在web开发中,由于需要在页面上或者脱离事务时使用到懒加载对应的对象,一般都采用Open Session In View模式。
Open Session In View
OpenSessionInView 模式用法探讨,在没有使用Spring提供的Open Session In View情况下,因需要在service(or Dao)层里把session关闭,所以lazy loading 为true的话,要在应用层内把关系集合都初始化,如company.getEmployees(),否则Hibernate抛session already closed Exception。
Open Session In View提供了一种简便的方法,较好地解决了lazy loading问题。它有两种配置方式OpenSessionInViewInterceptor和OpenSessionInViewFilter(具体参看SpringSide),功能相同,只是一个在web.xml配置,另一个在application.xml配置而已。
Open Session In View在request把session绑定到当前thread期间一直保持hibernate session在open状态,使session在request的整个期间都可以使用,如在View层里PO也可以lazy loading数据,如 ${ company.employees }。当View 层逻辑完成后,才会通过Filter的doFilter方法或Interceptor的postHandle方法自动关闭session。
Hibernate中自带了OpenSessionInViewFilter,我们可以直接在web.xml中对其进行配置:
<filter> <filter-name>openSessionInViewFilter</filter-name> <filter-class>org.springframework.orm.hibernate4.support.OpenSessionInViewFilter</filter-class> <init-param> <param-name>singleSession</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>openSessionInViewFilter</filter-name> <url-pattern>*.do</url-pattern> </filter-mapping>
这样就可以对所有的服务请求都进行过滤,并在其中绑定了SessionFactory,具体可以查看org.springframework.orm.hibernate4.support.OpenSessionInViewFilter源码。
由于定时任务(Cron)并不是通过请求过来的,不会走到Filter中,所以也不会在线程上下文中绑定sessionFactory,因此需要用其他的方式。如果使用OpenSessionInViewInterceptor绑定service,就会将所有的service再次重新绑定,也没有这个必要。
因此,这里我们仿照OpenSessionInViewInterceptor写了一个专门绑定到指定包的Interceptor来做这件事情,确定所有的cron服务都经过sessionFactory绑定即可。
@Component @Aspect public class OpenSessionInCronInterceptor { private static final Logger logger = Logger.getLogger(OpenSessionInCronInterceptor.class.getName()); @Autowired private SessionFactory sessionFactory; @Pointcut("execution(void com.ejiapei.service.cron.*.work())") protected void definePointcut() { } @Before("definePointcut()") public void preHandle() throws DataAccessException { logger.info("Opening Hibernate Session in OpenSessionInViewInterceptor"); Session session = openSession(); TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session)); } /** * Unbind the Hibernate <code>Session</code> from the thread and close it). * * @see org.springframework.transaction.support.TransactionSynchronizationManager */ @After("definePointcut()") public void afterCompletion() throws DataAccessException { SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory); logger.info("Closing Hibernate Session in OpenSessionInViewInterceptor"); SessionFactoryUtils.closeSession(sessionHolder.getSession()); } /** * Open a Session for the SessionFactory that this interceptor uses. * <p>The default implementation delegates to the * <code>SessionFactory.openSession</code> method and * sets the <code>Session</code>'s flush mode to "MANUAL". * * @return the Session to use * @throws org.springframework.dao.DataAccessResourceFailureException if the Session could not be created * @see org.hibernate.FlushMode#MANUAL */ protected Session openSession() throws DataAccessResourceFailureException { try { Session session = SessionFactoryUtils.openSession(sessionFactory); session.setFlushMode(FlushMode.MANUAL); return session; } catch (HibernateException ex) { throw new DataAccessResourceFailureException("Could not open Hibernate Session", ex); } } }
首先,我们需要使用@Autowird从当前的Spring容器中获取到绑定的SessionFactory,用来创建Session,这部分与OpenSessionInView Filter/Interceptor的代码比较类似,只不过spring提供的这两个方式做的事情实际上比我们这里需要的还要多。
这样,我们就可以在开始执行定时任务之前使用绑定的SessionFactory去重新开启一个Session,并绑定至SessionHolder,也就是该线程上线文中,就避免了懒加载的问题。
除此之外,还有一种比较暴力的方式,就是把所有懒加载的地方都修改成Lazy.EAGER,如果这样就改为非懒加载,但是可能会影响性能,因为影响了Hibernate访问数据库的方式。