OpenSessionInViewFilter ,Transactioninterceptor源码浅析

使用hibernate和spring的项目大部分都有可能会用到OpenSessionInViewFilter和Transactioninterceptor。OpenSessionInViewFilter的主要目的是为了解决hibernate在view层的进行懒加载的时候会遇到session被close的问题。Transactioninterceptor事务拦截器为spring的声明式事务,方便开启和提交事务,不用每次都手动编写hibernate事务相关代码。

先看OpenSessionInViewFilter,在web.xml中的filter配置如下:

web.xml

<filter>
        <filter-name>hibernateFilter</filter-name>
        <filter-class>
            org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
        </filter-class>
        <init-param>
            <param-name>sessionFactoryBeanName</param-name>
            <param-value>sessionFactory</param-value>
        </init-param>
        <init-param>
            <param-name>singleSession</param-name>
            <param-value>true</param-value>
        </init-param>
</filter>

初始化参数singleSession可选值有true(单session)和false(多session),打开源码,找到入口方法doFilterInternal如下:

OpenSessionInViewFilter

protected void doFilterInternal(
            HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        SessionFactory sessionFactory = lookupSessionFactory(request);
        boolean participate = false;
        if (isSingleSession()) {
             //此处省略n行代码......
            logger.debug("Opening single Hibernate Session in OpenSessionInViewFilter");
            Session session = getSession(sessionFactory);
            TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
        }
        else {
            //此处省略n行代码......
            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);
                }
            }
        }
    }

单session的情况下,请求开始时,先通过getSession方法取到一个session,然后将sessionFactory作为key,SessionHolder作为value绑定到resources中,请求结束的时候,解除绑定session并关闭session。多session的时候,请求开始时执行initDeferredClose方法标记请求过程中产生的所有session都延迟关闭,请求结束时才关闭所有的session,也就是关闭请求中所有被保存起来的session。中间的filterChain.doFilter(request, response)方法是执行其他filter的拦截方法以及请求对应的具体处理内容。

先看一下单session的bindResource方法如下:

bindResource

public static void bindResource(Object key, Object value) throws IllegalStateException {
        //此处省略n行代码......
        Map<Object, Object> map = resources.get();
        // set ThreadLocal Map if none found
        if (map == null) {
            map = new HashMap<Object, Object>();
            resources.set(map);
        }
        Object oldValue = map.put(actualKey, value);
        //此处省略n行代码......
}

可以发现resources是一个ThreadLocal类型的变量,其内部实现是一个Map,以当前线程作为key,来达到[整个请求线程共享变量并且多个请求之间不会相互影响]的作用:

ThreadLocal

private static final ThreadLocal<Map<Object, Object>> resources =
            new NamedThreadLocal<Map<Object, Object>>("Transactional resources");

单session情况下,请求开始就获取session,而多session时候则是执行的initDeferredClose这个方法:

initDeferredClose

public static void initDeferredClose(SessionFactory sessionFactory) {
        Assert.notNull(sessionFactory, "No SessionFactory specified");
        logger.debug("Initializing deferred close of Hibernate Sessions");
        Map<SessionFactory, Set<Session>> holderMap = deferredCloseHolder.get();
        if (holderMap == null) {
            holderMap = new HashMap<SessionFactory, Set<Session>>();
            deferredCloseHolder.set(holderMap);
        }
        holderMap.put(sessionFactory, new LinkedHashSet<Session>(4));
}

这个方法首先判断deferredCloseHolder中是否含有holderMap,如果没有,就新建一个map变量并set到deferredCloseHolder,这个map中的Set<Session>集合后面会用来保存请求过程中所有产生的session,请求中的数据库操作只要发现deferredCloseHolder中有值,就不会去关闭session,而是将session保存到set集合里,等到请求结束才关闭session。同样可以发现这个deferredCloseHolder也是一个ThreadLocal类型的变量:

deferredCloseHolder

private static final ThreadLocal<Map<SessionFactory, Set<Session>>> deferredCloseHolder =
            new NamedThreadLocal<Map<SessionFactory, Set<Session>>>("Hibernate Sessions registered for deferred close");

多session请求结束时候,执行延迟关闭方法:

processDeferredClose

public static void processDeferredClose(SessionFactory sessionFactory) {
        Assert.notNull(sessionFactory, "No SessionFactory specified");
        Map<SessionFactory, Set<Session>> holderMap = deferredCloseHolder.get();
        if (holderMap == null || !holderMap.containsKey(sessionFactory)) {
            throw new IllegalStateException("Deferred close not active for SessionFactory [" + sessionFactory + "]");
        }
        logger.debug("Processing deferred close of Hibernate Sessions");
        Set<Session> sessions = holderMap.remove(sessionFactory);
        for (Session session : sessions) {
            closeSession(session);
        }
        if (holderMap.isEmpty()) {
            deferredCloseHolder.remove();
        }
}

可以看到,此处的holderMap就是请求开始时往deferredCloseHolder中put的变量,然后从holderMap取出请求过程中产生的所有session并关闭。

接着回头看单session的时候,会用getSession方法取到一个sesson:

getSession

protected Session getSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException {
        Session session = SessionFactoryUtils.getSession(sessionFactory, true);
        FlushMode flushMode = getFlushMode();
        if (flushMode != null) {
            session.setFlushMode(flushMode);
        }
        return session;
}
private FlushMode flushMode = FlushMode.MANUAL;

此处有个细节就是,获取到session的时候会把这个session的FlushMode设置为默认的Manual状态,这个Manual状态会导致执行HibernateTemplate的增删改方法(无事务的dao方法)报错,只能执行查询方法。解决方案有两个:

  • 可以通过将HibernateTemplate的增删改方法(常称dao方法)该为执行spring事务拦截方法(常称service方法),事务拦截器会把session的flushMode改为auto,事务结束之后再重新设置为原来的Manual状态。

  • 新建一个java类继承OpenSessionInViewFilter,改写getSession方法的FlushMode,将该java类代替OpenSessionInViewFilter配置到web.xml中

跟踪到getSession方法的内部实现doGetSession方法,这个方法在HibernateTemplate的增删改查和事务拦截器Transactioninterceptor创建事务的时候也都会用到:

doGetSession

private static Session doGetSession(
            SessionFactory sessionFactory, Interceptor entityInterceptor,
            SQLExceptionTranslator jdbcExceptionTranslator, boolean allowCreate)
            throws HibernateException, IllegalStateException {

        Assert.notNull(sessionFactory, "No SessionFactory specified");

        Object resource = TransactionSynchronizationManager.getResource(sessionFactory);
        if (resource instanceof Session) {
            return (Session) resource;
        }
        SessionHolder sessionHolder = (SessionHolder) resource;
        if (sessionHolder != null && !sessionHolder.isEmpty()) {
            // pre-bound Hibernate Session
            Session session = null;
            if (TransactionSynchronizationManager.isSynchronizationActive() &&
                    sessionHolder.doesNotHoldNonDefaultSession()) {
                // Spring transaction management is active ->
                // register pre-bound Session with it for transactional flushing.
                session = sessionHolder.getValidatedSession();
                if (session != null && !sessionHolder.isSynchronizedWithTransaction()) {
                    logger.debug("Registering Spring transaction synchronization for existing Hibernate Session");
                    TransactionSynchronizationManager.registerSynchronization(
                            new SpringSessionSynchronization(sessionHolder, sessionFactory, jdbcExceptionTranslator, false));
                    sessionHolder.setSynchronizedWithTransaction(true);
                    // Switch to FlushMode.AUTO, as we have to assume a thread-bound Session
                    // with FlushMode.MANUAL, which needs to allow flushing within the transaction.
                    FlushMode flushMode = session.getFlushMode();
                    if (flushMode.lessThan(FlushMode.COMMIT) &&
                            !TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
                        session.setFlushMode(FlushMode.AUTO);
                        sessionHolder.setPreviousFlushMode(flushMode);
                    }
                }
            }
            else {
                // No Spring transaction management active -> try JTA transaction synchronization.
                session = getJtaSynchronizedSession(sessionHolder, sessionFactory, jdbcExceptionTranslator);
            }
            if (session != null) {
                return session;
            }
        }

        logger.debug("Opening Hibernate Session");
        Session session = (entityInterceptor != null ?
                sessionFactory.openSession(entityInterceptor) : sessionFactory.openSession());

        // Use same Session for further Hibernate actions within the transaction.
        // Thread object will get removed by synchronization at transaction completion.
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            // We're within a Spring-managed transaction, possibly from JtaTransactionManager.
            logger.debug("Registering Spring transaction synchronization for new Hibernate Session");
            SessionHolder holderToUse = sessionHolder;
            if (holderToUse == null) {
                holderToUse = new SessionHolder(session);
            }
            else {
                holderToUse.addSession(session);
            }
            if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
                session.setFlushMode(FlushMode.MANUAL);
            }
            TransactionSynchronizationManager.registerSynchronization(
                    new SpringSessionSynchronization(holderToUse, sessionFactory, jdbcExceptionTranslator, true));
            holderToUse.setSynchronizedWithTransaction(true);
            if (holderToUse != sessionHolder) {
                TransactionSynchronizationManager.bindResource(sessionFactory, holderToUse);
            }
        }
        else {
            // No Spring transaction management active -> try JTA transaction synchronization.
            registerJtaSynchronization(session, sessionFactory, jdbcExceptionTranslator, sessionHolder);
        }

        // Check whether we are allowed to return the Session.
        if (!allowCreate && !isSessionTransactional(session, sessionFactory)) {
            closeSession(session);
            throw new IllegalStateException("No Hibernate Session bound to thread, " +
                "and configuration does not allow creation of non-transactional one here");
        }

        return session;
}

这段代码比较长,主要逻辑如下:

如果ThreadLocal中有sessionHolder,说明前面已经在ThreadLocal中绑定过了session,直接从sessionHolder中取出sesision即可,不需要重新开启一个新的session,而是沿用之前绑定到ThreadLocal中的sessoin。如果ThreadLocal中没有sessionHolder,说明没有绑定sesison,就通过
SessionFactoryUtils开启一个新的session,得到这个session之后再判断当前是否存在spring事务,如果有就绑定到ThreadLocal中,之后在这个事务范围内的dao操作都共用这个session,如果没有事务,就直接返回这个session。

回过头去看OpenSessionInViewFilter 在单session的情况下,通过getSession方法获取到session之后,就执行bindResource方法将session绑定到ThreadLocal中了:

TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));

到此就可以得出几个小结论:

  • 单sessoin情况下,请求一开始就会创建session并绑定到ThreadLocal变量中,请求过程中需要用到hibernate的sesison进行数据库操作的时候,就直接从ThreadLocal变量中取出复用,请求结束了就关闭这个唯一的session,整个请求过程也只会占用一个数据库链接。

  • 多session的情况下,请求刚开始并不创建session,而是等到请求过程中需要session的时候就open一个session,执行多个HibernateTemplate的增删改查方法就会创建多个session,请求过程中将会占用多个数据库链接,并直到请求结束链接才会被释放。

然后接着看HibernateTemplate的增删改查代码对session的处理情况,举个save方法的例子:

save

public Serializable save(final Object entity) throws DataAccessException {
        return executeWithNativeSession(new HibernateCallback<Serializable>() {
            public Serializable doInHibernate(Session session) throws HibernateException {
                checkWriteOperationAllowed(session);
                return session.save(entity);
            }
        });
}

增删改查都会调用executeWithNativeSession方法,并传入一个HibernateCallback实例,执行相应的增删改查操作。executeWithNativeSession方法属于设计模式中的命令模式,简单来说就是往这个方法传入什么命令,hibernate就帮你执行什么操作。继续往里面跟踪代码可以看到:

doExecute

public <T> T executeWithNativeSession(HibernateCallback<T> action) {
        return doExecute(action, false, true);
}

protected <T> T doExecute(HibernateCallback<T> action, boolean enforceNewSession, boolean enforceNativeSession)
            throws DataAccessException {

        Assert.notNull(action, "Callback object must not be null");

        Session session = (enforceNewSession ?
                SessionFactoryUtils.getNewSession(getSessionFactory(), getEntityInterceptor()) : getSession());
        boolean existingTransaction = (!enforceNewSession &&
                (!isAllowCreate() || SessionFactoryUtils.isSessionTransactional(session, getSessionFactory())));
        if (existingTransaction) {
            logger.debug("Found thread-bound Session for HibernateTemplate");
        }

        FlushMode previousFlushMode = null;
        try {
            previousFlushMode = applyFlushMode(session, existingTransaction);
            enableFilters(session);
            Session sessionToExpose =
                    (enforceNativeSession || isExposeNativeSession() ? session : createSessionProxy(session));
            T 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()) {
                    SessionFactoryUtils.closeSession(session);
                }
                else {
                    SessionFactoryUtils.closeSessionOrRegisterDeferredClose(session, getSessionFactory());
                }
            }
        }
}

这个方法就比较明了了:
首先获取hibernate的session,就是上面分析过的那个方法。然后判断existingTransaction,也就是当前是否存在spring事务。可以看到previousFlushMode = applyFlushMode(session, existingTransaction),这行代码,如果当前存在事务,就会修改session的flushMode,并取得修改之前的previousFlushMode,执行增删改查操作之后再把这个previousFlushMode,也就是旧的flushMode设置回session中,这也就是为什么在单session情况下,有spring事务的可以执行增删改操作,而没有spring事务执行增删改就会报错的原因。

中间的:

T result = action.doInHibernate(sessionToExpose);

此处就是执行的增删改操作,以及返回操作结果。hinernate创建session的时候并不会占用数据库链接,只有hibernate真正执行与数据库交互的时候才会申请并占用数据库链接。在执行action.doInHibernate的时候hibernate其实还没有开始真正的进行数据库操作,只是在内存中操作,只有执行session.flush()的时候才会把内存中的这些数据真正的同步到数据库中,也就是执行session.flush()的时候才会开始占用数据库链接。当然查询方法是个例外,因为必须要从数据库中取出数据,所以如果是查询方法,在查询的时候就会与数据库有交互操作,也就是进行查询的时候就会开始占用数据库链接。接下来的代码就是判断是否要进行flush操作,继续跟踪到flushIfNecessary这个方法中:

flushIfNecessary

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();
        }
}

可以看到,当flushMode为FLUSH_EAGER的时候,或者不存在spring事务且flushMode不为FLUSH_NEVER的时候就会进行session.flush()操作,同步数据。综上可见在存在spring事务的情况下,所有dao的增删改操作都不会马上被同步到数据库中,而要等到事务提交才会被flush到数据库。所有如果你在某个带有spring事务的service方法内部对dao执行try catch会出现捕获不到数据库异常的情况:

public calss Service{
    public void saveObj(){
        try{
            dao.save(dto);
        }catch(Exception e){
           //这里catch不到数据库错误
        }
    }
}

public calss Action{
    public void saveSomeThing(){
        try{
            service.saveObj(dto);
        }catch(Exception e){
           //这里才能catch到数据库错误
        }
    }
}

执行完增删改查之后,如果当前有spring事务就把flushMode设置为原来的状态:session.setFlushMode(previousFlushMode)。如果当前没有spring事务,会看到以下方法:

SessionFactoryUtils.closeSessionOrRegisterDeferredClose(session, getSessionFactory());

closeSessionOrRegisterDeferredClose

static void closeSessionOrRegisterDeferredClose(Session session, SessionFactory sessionFactory) {
        Map<SessionFactory, Set<Session>> holderMap = deferredCloseHolder.get();
        if (holderMap != null && sessionFactory != null && holderMap.containsKey(sessionFactory)) {
            logger.debug("Registering Hibernate Session for deferred close");
            // Switch Session to FlushMode.MANUAL for remaining lifetime.
            session.setFlushMode(FlushMode.MANUAL);
            Set<Session> sessions = holderMap.get(sessionFactory);
            sessions.add(session);
        }
        else {
            closeSession(session);
        }
}

从这个方法就可以很清晰的看出在OpenSessionInViewFilter多session的情况下,只要deferredCloseHolder有holderMap,session就不会被关闭,而是被保存到Set<Session>集合中,也就是多session情况下数据库链接在请求结束之前都不会被释放的原因。

最后是Transactioninterceptor拦截器,入口方法如下:

public Object invoke(final MethodInvocation invocation) throws Throwable {
        Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
        final TransactionAttribute txAttr =
                getTransactionAttributeSource().getTransactionAttribute(invocation.getMethod(), targetClass);
        final PlatformTransactionManager tm = determineTransactionManager(txAttr);
        final String joinpointIdentification = methodIdentification(invocation.getMethod(), targetClass);
        if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
            TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
            Object retVal = null;
            try {
                retVal = invocation.proceed();
            }
            catch (Throwable ex) {
                // target invocation exception
                completeTransactionAfterThrowing(txInfo, ex);
                throw ex;
            }
            finally {
                cleanupTransactionInfo(txInfo);
            }
            commitTransactionAfterReturning(txInfo);
            return retVal;
        }
        //此处省略n行代码......
}

retVal = invocation.proceed()
这行代码是执行拦截器链中的其他拦截器以及被拦截的目标方法。这个方法前面的内容就是开启事务,后面的内容就是提交和回滚事务。也就是spring声明式事务可以不用手动开启和提交事务的原理。createTransactionIfNecessary这个方法就是根据applicationcontext.xml的配置来生成不同类型的事务,而commitTransactionAfterReturning就是提交事务。

跟踪createTransactionIfNecessary进入到getTransaction方法:

getTransaction

public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
        Object transaction = doGetTransaction();

        // Cache debug flag to avoid repeated checks.
        boolean debugEnabled = logger.isDebugEnabled();

        if (definition == null) {
            // Use defaults if no transaction definition given.
            definition = new DefaultTransactionDefinition();
        }

        if (isExistingTransaction(transaction)) {
            // Existing transaction found -> check propagation behavior to find out how to behave.
            return handleExistingTransaction(definition, transaction, debugEnabled);
        }

        // Check definition settings for new transaction.
        if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
            throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
        }

        // No existing transaction found -> check propagation behavior to find out how to proceed.
        if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
            throw new IllegalTransactionStateException(
                    "No existing transaction found for transaction marked with propagation 'mandatory'");
        }
        else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
                definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
            definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
            SuspendedResourcesHolder suspendedResources = suspend(null);
            if (debugEnabled) {
                logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
            }
            try {
                boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
                DefaultTransactionStatus status = newTransactionStatus(
                        definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
                doBegin(transaction, definition);
                prepareSynchronization(status, definition);
                return status;
            }
            catch (RuntimeException ex) {
                resume(null, suspendedResources);
                throw ex;
            }
            catch (Error err) {
                resume(null, suspendedResources);
                throw err;
            }
        }
        else {
            // Create "empty" transaction: no actual transaction, but potentially synchronization.
            boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
            return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
        }
}

由于spring提供了多种事务嵌套的传播机制,所以这段代码里面有许多if else的判断,开启事务在doBegin这个方法里面。这里需要注意最后那个else分支:创建一个空的事务,并非真正的数据库事务,也就是不执行session.begintransaction()这个开启事务的方法。spring事务传播有个配置叫PROPAGATION_NOT_SUPPORTED:意思是以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。PROPAGATION_NOT_SUPPORTED这个配置就会走到最后的这个else分支。虽然以非事务方式运行,但是在前面分析的许多用来判断当前是否存在spring事务的existingTransaction这个变量的值依旧会是ture。也就是说,spring认为当前存在事务,虽然是一个空事务。这就导致可能出现下面的情况:

  • 在某个service中配置为PROPAGATION_NOT_SUPPORTED的方法中开始执行了查数据库的操作,占用了一个数据库链接,然后又把查询出来的数据作为参数调用了一个耗时很长的外围系统的接口,这样就会导致这个空事务一直不被提交,这个数据库链接也就会被一直被占用而无法释放。

继续看开启事务的doBegin方法:

doBegin

@Override
    protected void doBegin(Object transaction, TransactionDefinition definition) {
        //此处省略n行代码......
        hibTx = session.beginTransaction();
        //此处省略n行代码......
}

执行完目标方法,中间有一个准备提交事务的方法:

prepareForCommit

protected void prepareForCommit(DefaultTransactionStatus status) {
        if (this.earlyFlushBeforeCommit && status.isNewTransaction()) {
            HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
            Session session = txObject.getSessionHolder().getSession();
            if (!session.getFlushMode().lessThan(FlushMode.COMMIT)) {
                logger.debug("Performing an early flush for Hibernate transaction");
                try {
                    session.flush();
                }
                catch (HibernateException ex) {
                    throw convertHibernateAccessException(ex);
                }
                finally {
                    session.setFlushMode(FlushMode.MANUAL);
                }
            }
        }
}

这里可以看到 session.flush()方法,也就是前面说的事务开始提交的时候才会把hibernate内存中的数据同步到数据库之中。

最后就是事务提交完成之后的处理session.disconnect()或者session.close()释放数据库链接,如下:

doCleanupAfterCompletion

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.isNewSession()) {
            if (logger.isDebugEnabled()) {
                logger.debug("Closing Hibernate Session [" + SessionFactoryUtils.toString(session) +
                        "] after transaction");
            }
            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 (!this.hibernateManagedSession) {
                session.disconnect();
            }
        }
        txObject.getSessionHolder().clear();
}
  • 多session的情况下,如果没有嵌套事务,txObject.isNewSessionHolder()和txObject.isNewSession()的返回值为true,会执行unbindResource操作和closeSessionOrRegisterDeferredClose操作。
  • 单session情况下不解除绑定,后面依然可以从ThreadLocal中获取到session,但是会执行session.disconnect()来关闭数据库链接。

下面给出各种情况下的结论:

  1. 在单session情况下,session会被绑定至当前线程(ThreadLocal),整个请求过程中共用一个sesssion,整个请求过程至多一个数据库连接。如果执行带有spring事务的service方法结束之后会触发session.disconnect()释放连接。虽然只有一个连接,但如果在service内部方法结束之前调用了外围耗时的操作会导致连接长时间不被释放。

  2. 多session情况下,每执行一个dao或者service方法都将创建一个session,每个session都将占用一个数据库连接,dao和service方法结束都不会关闭session,直到请求结束才会释放连接。service内部的所有dao操作共用一个sesison。如果在for循环中调用dao或者service,将产生n个连接。所以,这种模式下一次请求将占用n个连接,将会导致数据库连接瞬间飙升的情况。

  3. 后台自启线程,如quartz定时调度等不经过OpenSessionInViewFilter的情况下,每执行一个dao或者service方法都将创建一个session,执行结束之后都会关闭sessoin。service内部的所有dao操作共用一个sesison。所以即使在for循环里面调用dao或者service,同一时刻也只会占用一个数据库连接,不会导致数据库连接飙升的情况。

  4. 创建hibernate的sessoin的时候并不占用数据库连接,执行查询方法或者session的flush方法才会开始占用数据库连接。spring事务开启的时候,由于需要调用jdbc底层的Connection.setAutoCommit方法,所以spring事务开启的时候,即使中间没有执行任何数据库操作,也会占用数据库连接。如果service方法的propagation为PROPAGATION_NOT_SUPPORTED,虽有spring事务(空事务),但是由于不会创建JDBC底层事务,所以这类的service方法开始的时候不会占用数据库连接。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 这部分主要是开源Java EE框架方面的内容,包括Hibernate、MyBatis、Spring、Spring ...
    杂货铺老板阅读 1,270评论 0 2
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,360评论 6 343
  • 人之初,性本善,这是一种认识,对错各半! 婴儿生来不知善恶,不断长大会受父母的耳濡目染,外在环境更是会给孩子带来重...
    小小星火阅读 252评论 0 0
  • 今天的事情太多,让我本来打算休养生息的计划彻底搁浅,不得已只能在外面奔波了一整天。从参加同事女儿的满月酒,到感到大...
    夏野阅读 154评论 0 0