最近在做一个web项目 (ssh) 在操作数据层时 为了方便 不想通过启动web服务器 通过前台点击调用。 于是就想到了 使用main方法 把它做为一个普通的application 项目进行启动调用。 但是 在对数据库进行操作的时候报 LazyInitializationException 异常
具体异常信息:
2014-08-26 17:40:54 ERROR LazyInitializationException:19 - could not initialize proxy - no Session
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:57)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:111)
at org.hibernate.proxy.pojo.cglib.CGLIBLazyInitializer.invoke(CGLIBLazyInitializer.java:150)
at com.resource.happok.common.domain.Comment$$EnhancerByCGLIB$$1a705b58.getUser(<generated>)
at com.resource.happok.common.util.Test.main(Test.java:35)
Exception in thread "main" org.hibernate.LazyInitializationException: could not initialize proxy - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:57)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:111)
at org.hibernate.proxy.pojo.cglib.CGLIBLazyInitializer.invoke(CGLIBLazyInitializer.java:150)
at com.resource.happok.common.domain.Comment$$EnhancerByCGLIB$$1a705b58.getUser(<generated>)
at com.resource.happok.common.util.Test.main(Test.java:35)
异常原因: Indicates access to unfetched data outside of a session context.
hibernate 在sesssion访问之外访问了数据。(也就是seeeion已关闭了)
这个是hibernate的延迟加载异常,
Hibernate 允许对关联对象、属性进行延迟加载,但是必须保证延迟加载的操作限于同一个 Hibernate Session 范围之内进行。如果 Service 层返回一个启用了延迟加载功能的领域对象给 Web 层,当 Web 层访问到那些需要延迟加载的数据时,由于加载领域对象的 Hibernate Session 已经关闭,这些导致延迟加载数据的访问异常
为了不产生该异常,有如下解决方法:
1、在相应的映射文件里禁止该类的延迟加载:设置lazy=false
2、在session关闭之前取出需要的属性
3、使用openSessionInView
法一: 为了一个测试修改项目的源码不靠谱,而且取消了延迟加载会增加不必要的开销。
法二: session关闭之前取出数据 也是要修改项目源码 将load()改成get(); 太麻烦 和方法一一样。
法三: 使用openSessionInView
在web.xml 文件加入如下配置信息
<filter>
<filter-name>openSessionInView</filter-name>
<filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>openSessionInView</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
但这是在web项目中的解决方法 我该怎么使用这种方法在application项目中解决该问题呢?
不妨我们先看看openSessionInView.java 这个类是干什么的 主要实现了什么功能。
下面是openSessionInView 的doFilterInternal方法
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
SessionFactory sessionFactory = lookupSessionFactory(request);
boolean participate = false;
if (isSingleSession()) {
// single session mode
if (TransactionSynchronizationManager.hasResource(sessionFactory)) {
// Do not modify the Session: just set the participate flag.
participate = true;
}
else {
logger.debug("Opening single Hibernate Session in OpenSessionInViewFilter");
Session session = getSession(sessionFactory);
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
}
}
else {
// deferred close mode
if (SessionFactoryUtils.isDeferredCloseActive(sessionFactory)) {
// Do not modify deferred close: just set the participate flag.
participate = true;
}
else {
SessionFactoryUtils.initDeferredClose(sessionFactory);
}
}
try {
filterChain.doFilter(request, response);
}
finally {
if (!participate) {
if (isSingleSession()) {
// single session mode
SessionHolder sessionHolder =
(SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
logger.debug("Closing single Hibernate Session in OpenSessionInViewFilter");
closeSession(sessionHolder.getSession(), sessionFactory);
}
else {
// deferred close mode
SessionFactoryUtils.processDeferredClose(sessionFactory);
}
}
}
}
这个方法 主要是把一个Hibernate Session和一次完整的请求过程对应的线程相绑定。
找到了原因 接下来就好办了 我们可以自己写一个类似的方法 在每次执行数据库操作的时候调用。方法如下:
public static void before () {
//手动编码打开session
Session session = sessionFactory.openSession();
//将session保存在SessionHolder ,并由TransactionSynchronizationManager进行管理
//保存
SessionHolder sessionHolder = new SessionHolder(session);
//管理,绑定到本地线程
TransactionSynchronizationManager.bindResource(sessionFactory,sessionHolder);
}
既能开启了session 并且绑定到了本地线程 那么就得有个解除解除绑定和关闭session的操作
方法如下
public static void after() {
SessionHolder sessionHolder = (SessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
Session session = sessionHolder.getSession();
//清除
session.flush();
//从本地线程中取消绑定
TransactionSynchronizationManager.unbindResource(sessionFactory);
//关闭连接
SessionFactoryUtils.closeSession(session);
}
这个在main方法中 进行数据库操作前执行before() 操作完成之后 执行after()方法 这样就不会报错了~ 问题解决
我的具体代码
package com.resource.happok.common.util;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.orm.hibernate3.SessionFactoryUtils;
import org.springframework.orm.hibernate3.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import com.resource.happok.common.domain.Comment;
import com.resource.happok.school.service.impl.CommentServiceImpl;
public class Test {
/**
* @param args
*/
private static SessionFactory sessionFactory;
public static void main(String[] args)throws Exception {
String[] array = {"springContext/applicationContext-*.xml"};
ApplicationContext atcx=new ClassPathXmlApplicationContext(array);
sessionFactory = (SessionFactory) atcx.getBean("sessionFactory");
System.out.println("sessionFactory>>" + sessionFactory);
before();
CommentServiceImpl commentServiceImpl = (CommentServiceImpl)atcx.getBean("commentServiceImpl");
Comment comment = commentServiceImpl.findById(new Long(1));
System.out.println(comment.getUser().getUserName());
System.out.println(comment.getUser().getImage());
after();
}
/**
* 将session和线程绑定 在执行数据库操作之前调用
*/
public static void before () {
//手动编码打开session
Session session = sessionFactory.openSession();
//将session保存在SessionHolder ,并由TransactionSynchronizationManager进行管理
//保存
SessionHolder sessionHolder = new SessionHolder(session);
//管理,绑定到本地线程
TransactionSynchronizationManager.bindResource(sessionFactory,sessionHolder);
}
/**
* 将session和线程解除绑定 关闭session 在执行数据库操作之后调用
*/
public static void after() {
SessionHolder sessionHolder = (SessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
Session session = sessionHolder.getSession();
//清除
session.flush();
//从本地线程中取消绑定
TransactionSynchronizationManager.unbindResource(sessionFactory);
//关闭连接
SessionFactoryUtils.closeSession(session);
}
}
参考文章
1. http://www.itzhai.com/org-hibernate-lazyinitializationexception-solution-using-opensessioninviewfilter.html
2. http://www.360doc.com/content/13/0531/15/2703996_289489930.shtml
3. http://xiaoxuejie.iteye.com/blog/789092