ERROR] - could not initialize proxy - the owning Session was closed - [org.hibernate.LazyInitializationException.<init>(LazyInitializationException.java:19)]
用结合spring 使用hibernate都会遇到的问题, 为了避免多余的关联查询, lazy loading的引入, 但是通常持久层会把session关闭了, render view的时候PO 里面的session其实已经关闭了, 再load其他关联属性, 异常就出来了.
单独使用hibernate的事务管理吧. http://www.hibernate.org/42.html
以下讨论话题都是基于Spring + Hibernate配套使用.
1. session在哪里给关闭了?
(1) 假设没有配置OpenSessionInViewFilter, 以及HibernateTransactionManager,AOP任何事务。 我们只是使用HibernateTemplate 在DAO层里做若干次查询操作, 跟踪日志.
2009-03-11 14:28:51,759 [DEBUG] - #{genericFO.signIn} - [com....web.AccessControlActionListenerImpl.preProcessAction(AccessControlActionListenerImpl.java:66)]
2009-03-11 14:28:52,259 [DEBUG] - Opening Hibernate Session - [org.springframework.orm.hibernate3.SessionFactoryUtils.doGetSession(SessionFactoryUtils.java:316)]
2009-03-11 14:28:52,852 [DEBUG] - Eagerly flushing Hibernate session - [org.springframework.orm.hibernate3.HibernateAccessor.flushIfNecessary(HibernateAccessor.java:389)]
2009-03-11 14:28:52,868 [DEBUG] - Closing Hibernate Session - [org.springframework.orm.hibernate3.SessionFactoryUtils.closeSession(SessionFactoryUtils.java:772)]
2009-03-11 14:28:52,899 [DEBUG] - Opening Hibernate Session - [org.springframework.orm.hibernate3.SessionFactoryUtils.doGetSession(SessionFactoryUtils.java:316)]
2009-03-11 14:28:53,040 [DEBUG] - Eagerly flushing Hibernate session - [org.springframework.orm.hibernate3.HibernateAccessor.flushIfNecessary(HibernateAccessor.java:389)]
2009-03-11 14:28:53,040 [DEBUG] - Closing Hibernate Session - [org.springframework.orm.hibernate3.SessionFactoryUtils.closeSession(SessionFactoryUtils.java:772)]
2009-03-11 14:28:53,056 [ERROR] - could not initialize proxy - the owning Session was closed - [org.hibernate.LazyInitializationException.<init>(LazyInitializationException.java:19)]
SessionFactoryUtils line 316
logger.debug("Opening Hibernate Session");
Session session = (entityInterceptor != null ?
sessionFactory.openSession(entityInterceptor) : sessionFactory.openSession());
可以跟一下实现类SessionFactoryImpl, openSession可是每次new 一个Session。
private SessionImpl openSession(
Connection connection,
boolean autoClose,
long timestamp,
Interceptor sessionLocalInterceptor
) {
return new SessionImpl(
connection,
this,
autoClose,
timestamp,
sessionLocalInterceptor == null ? interceptor : sessionLocalInterceptor,
settings.getDefaultEntityMode(),
settings.isFlushBeforeCompletionEnabled(),
settings.isAutoCloseSessionEnabled(),
settings.getConnectionReleaseMode()
);
}
SessionFactory的openSession和getCurrentSession区别比较大的, 一般currentSession有个实现是和threadLocal绑定的, 也就是说每个请求多次查询数据库可能使用的都是同一个session.
心疼了吧, 什么都不配置直接使用HibernateTemplate是比较耗的. 而且currentSession的实现在事务之后会保证session关闭(至少CurrentSessionContext这个接口是这样定义的)
顺便瞄一下HibernateTemplate查询调用的代码吧.
/**
* Execute the action specified by the given action object within a Session.
* @param action callback object that specifies the Hibernate action
* @param exposeNativeSession whether to expose the native Hibernate Session
* to callback code
* @return a result object returned by the action, or <code>null</code>
* @throws org.springframework.dao.DataAccessException in case of Hibernate errors
*/
public Object execute(HibernateCallback action, boolean exposeNativeSession) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
Session session = getSession();//每次都是openSession, 因为ThreadLocal没同一个事务的session
boolean existingTransaction = SessionFactoryUtils.isSessionTransactional(session, getSessionFactory());//为false, 因为threadLocal没有session
if (existingTransaction) {
logger.debug("Found thread-bound Session for HibernateTemplate");
}
FlushMode previousFlushMode = null;
try {
previousFlushMode = applyFlushMode(session, existingTransaction);
enableFilters(session);
Session sessionToExpose = (exposeNativeSession ? session : createSessionProxy(session));
Object result = action.doInHibernate(sessionToExpose);
flushIfNecessary(session, existingTransaction);
return result;
}
catch (HibernateException ex) {
throw convertHibernateAccessException(ex);
}
catch (SQLException ex) {
throw convertJdbcAccessException(ex);
}
catch (RuntimeException ex) {
// Callback code threw application exception...
throw ex;
}
finally {
if (existingTransaction) {
logger.debug("Not closing pre-bound Hibernate Session after HibernateTemplate");
disableFilters(session);
if (previousFlushMode != null) {
session.setFlushMode(previousFlushMode);
}
}
else {
// Never use deferred close for an explicitly new Session.
if (isAlwaysUseNewSession()) {//默认配置alwaysUseNewSession=false
SessionFactoryUtils.closeSession(session);
}
else {//每次都在这里关闭
SessionFactoryUtils.closeSessionOrRegisterDeferredClose(session, getSessionFactory());
}
}
}
}
(2) 那我们代码事务或用AOP配置的声明事务看下,主要就是看下HibernateTransactionmanager的使用了其实。假设使用的是TransactionDefinition.PROPAGATION_REQUIRED
public final TransactionStatus getTransaction(TransactionDefinition definition) {
}
void commit(TransactionStatus status) {
..processCommit(DefaultTransactionStatus status);...
}
processCommit(DefaultTransactionStatus status) {...
finally {
cleanupAfterCompletion(status);
}
}
private void cleanupAfterCompletion(DefaultTransactionStatus status) {
status.setCompleted();
if (status.isNewSynchronization()) {
TransactionSynchronizationManager.clear();
}
if (status.isNewTransaction()) {
doCleanupAfterCompletion(status.getTransaction());
}
if (status.getSuspendedResources() != null) {
if (status.isDebug()) {
logger.debug("Resuming suspended transaction");
}
resume(status.getTransaction(), (SuspendedResourcesHolder) status.getSuspendedResources());
}
}
其实调用的是Hibernate Session的Transaction, 默认的应该就是JDBCTransaction
protected void doCleanupAfterCompletion(Object transaction) {
HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
// Remove the session holder from the thread.
if (txObject.isNewSessionHolder()) {
TransactionSynchronizationManager.unbindResource(getSessionFactory());
}
// Remove the JDBC connection holder from the thread, if exposed.
if (getDataSource() != null) {
TransactionSynchronizationManager.unbindResource(getDataSource());
}
Session session = txObject.getSessionHolder().getSession();
if (this.prepareConnection && session.isConnected() && isSameConnectionForEntireSession(session)) {
// We're running with connection release mode "on_close": We're able to reset
// the isolation level and/or read-only flag of the JDBC Connection here.
// Else, we need to rely on the connection pool to perform proper cleanup.
try {
Connection con = session.connection();
DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel());
}
catch (HibernateException ex) {
logger.debug("Could not access JDBC Connection of Hibernate Session", ex);
}
}
if (txObject.isNewSessionHolder()) {//如果没用OpenSessionInViewFilter,这个txObject是newSessionHolder=true,否则为false
if (logger.isDebugEnabled()) {
logger.debug("Closing Hibernate Session [" + SessionFactoryUtils.toString(session) +
"] after transaction");
}//deferredClose空,所以还是关了session
SessionFactoryUtils.closeSessionOrRegisterDeferredClose(session, getSessionFactory());
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Not closing pre-bound Hibernate Session [" +
SessionFactoryUtils.toString(session) + "] after transaction");
}
if (txObject.getSessionHolder().getPreviousFlushMode() != null) {
session.setFlushMode(txObject.getSessionHolder().getPreviousFlushMode());
}
if (hibernateSetTimeoutAvailable) {
// Running against Hibernate 3.1+...
// Let's explicitly disconnect the Session to provide efficient Connection handling
// even with connection release mode "on_close". The Session will automatically
// obtain a new Connection in case of further database access.
// Couldn't do this on Hibernate 3.0, where disconnect required a manual reconnect.
session.disconnect();
}
}
txObject.getSessionHolder().clear();
}
可以简单这样认为, 由于事务是新的(因为ThreadLocal的SessionHolder是新创的,事先不存在), 事务完成后session还是关了.
其实session关了是好习惯啊,就好像用完了连接就关闭一样。
2. 怎么让session在view也是active?
OpenSessionInViewFilter,
protected void 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));
//ThreadLocal保存一个SessionHolder, 之后在事务HibernateTransactionManager中就认为NewSessionHolder=false, 清理的时候就关不了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);//下一个filter, 如果没filter就是要采访的jsp/servlet了
}
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);
}
}
}
}
很多人说的一个诟病就是session跨多个层, 特别是在页面渲染的时候, 写到客户端时pending时间长, session资源没及时释放, 或者有一定的道理, 很可惜没看到数据证明。
老实说, 这么一个问题的引入也证明一点, 没什么框架是完美的真那么傻瓜的, 适合使用, 简单就好。
Hibernate也只是我们项目中持久层的一个应用框架.