OpenSessionInViewFilter ,Transactioninterceptor源碼淺析

使用hibernate和spring的項(xiàng)目大部分都有可能會(huì)用到OpenSessionInViewFilter和Transactioninterceptor。OpenSessionInViewFilter的主要目的是為了解決hibernate在view層的進(jìn)行懶加載的時(shí)候會(huì)遇到session被close的問(wèn)題。Transactioninterceptor事務(wù)攔截器為spring的聲明式事務(wù),方便開(kāi)啟和提交事務(wù),不用每次都手動(dòng)編寫hibernate事務(wù)相關(guān)代碼。

先看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>

初始化參數(shù)singleSession可選值有true(單session)和false(多session),打開(kāi)源碼,找到入口方法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的情況下,請(qǐng)求開(kāi)始時(shí),先通過(guò)getSession方法取到一個(gè)session,然后將sessionFactory作為key,SessionHolder作為value綁定到resources中,請(qǐng)求結(jié)束的時(shí)候,解除綁定session并關(guān)閉session。多session的時(shí)候,請(qǐng)求開(kāi)始時(shí)執(zhí)行initDeferredClose方法標(biāo)記請(qǐng)求過(guò)程中產(chǎn)生的所有session都延遲關(guān)閉,請(qǐng)求結(jié)束時(shí)才關(guān)閉所有的session,也就是關(guān)閉請(qǐng)求中所有被保存起來(lái)的session。中間的filterChain.doFilter(request, response)方法是執(zhí)行其他filter的攔截方法以及請(qǐng)求對(duì)應(yīng)的具體處理內(nèi)容。

先看一下單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行代碼......
}

可以發(fā)現(xiàn)resources是一個(gè)ThreadLocal類型的變量,其內(nèi)部實(shí)現(xiàn)是一個(gè)Map,以當(dāng)前線程作為key,來(lái)達(dá)到[整個(gè)請(qǐng)求線程共享變量并且多個(gè)請(qǐng)求之間不會(huì)相互影響]的作用:

ThreadLocal

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

單session情況下,請(qǐng)求開(kāi)始就獲取session,而多session時(shí)候則是執(zhí)行的initDeferredClose這個(gè)方法:

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

這個(gè)方法首先判斷deferredCloseHolder中是否含有holderMap,如果沒(méi)有,就新建一個(gè)map變量并set到deferredCloseHolder,這個(gè)map中的Set<Session>集合后面會(huì)用來(lái)保存請(qǐng)求過(guò)程中所有產(chǎn)生的session,請(qǐng)求中的數(shù)據(jù)庫(kù)操作只要發(fā)現(xiàn)deferredCloseHolder中有值,就不會(huì)去關(guān)閉session,而是將session保存到set集合里,等到請(qǐng)求結(jié)束才關(guān)閉session。同樣可以發(fā)現(xiàn)這個(gè)deferredCloseHolder也是一個(gè)ThreadLocal類型的變量:

deferredCloseHolder

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

多session請(qǐng)求結(jié)束時(shí)候,執(zhí)行延遲關(guān)閉方法:

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就是請(qǐng)求開(kāi)始時(shí)往deferredCloseHolder中put的變量,然后從holderMap取出請(qǐng)求過(guò)程中產(chǎn)生的所有session并關(guān)閉。

接著回頭看單session的時(shí)候,會(huì)用getSession方法取到一個(gè)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;

此處有個(gè)細(xì)節(jié)就是,獲取到session的時(shí)候會(huì)把這個(gè)session的FlushMode設(shè)置為默認(rèn)的Manual狀態(tài),這個(gè)Manual狀態(tài)會(huì)導(dǎo)致執(zhí)行HibernateTemplate的增刪改方法(無(wú)事務(wù)的dao方法)報(bào)錯(cuò),只能執(zhí)行查詢方法。解決方案有兩個(gè):

  • 可以通過(guò)將HibernateTemplate的增刪改方法(常稱dao方法)該為執(zhí)行spring事務(wù)攔截方法(常稱service方法),事務(wù)攔截器會(huì)把session的flushMode改為auto,事務(wù)結(jié)束之后再重新設(shè)置為原來(lái)的Manual狀態(tài)。

  • 新建一個(gè)java類繼承OpenSessionInViewFilter,改寫getSession方法的FlushMode,將該java類代替OpenSessionInViewFilter配置到web.xml中

跟蹤到getSession方法的內(nèi)部實(shí)現(xiàn)doGetSession方法,這個(gè)方法在HibernateTemplate的增刪改查和事務(wù)攔截器Transactioninterceptor創(chuàng)建事務(wù)的時(shí)候也都會(huì)用到:

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

這段代碼比較長(zhǎng),主要邏輯如下:

如果ThreadLocal中有sessionHolder,說(shuō)明前面已經(jīng)在ThreadLocal中綁定過(guò)了session,直接從sessionHolder中取出sesision即可,不需要重新開(kāi)啟一個(gè)新的session,而是沿用之前綁定到ThreadLocal中的sessoin。如果ThreadLocal中沒(méi)有sessionHolder,說(shuō)明沒(méi)有綁定sesison,就通過(guò)
SessionFactoryUtils開(kāi)啟一個(gè)新的session,得到這個(gè)session之后再判斷當(dāng)前是否存在spring事務(wù),如果有就綁定到ThreadLocal中,之后在這個(gè)事務(wù)范圍內(nèi)的dao操作都共用這個(gè)session,如果沒(méi)有事務(wù),就直接返回這個(gè)session。

回過(guò)頭去看OpenSessionInViewFilter 在單session的情況下,通過(guò)getSession方法獲取到session之后,就執(zhí)行bindResource方法將session綁定到ThreadLocal中了:

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

到此就可以得出幾個(gè)小結(jié)論:

  • 單sessoin情況下,請(qǐng)求一開(kāi)始就會(huì)創(chuàng)建session并綁定到ThreadLocal變量中,請(qǐng)求過(guò)程中需要用到hibernate的sesison進(jìn)行數(shù)據(jù)庫(kù)操作的時(shí)候,就直接從ThreadLocal變量中取出復(fù)用,請(qǐng)求結(jié)束了就關(guān)閉這個(gè)唯一的session,整個(gè)請(qǐng)求過(guò)程也只會(huì)占用一個(gè)數(shù)據(jù)庫(kù)鏈接。

  • 多session的情況下,請(qǐng)求剛開(kāi)始并不創(chuàng)建session,而是等到請(qǐng)求過(guò)程中需要session的時(shí)候就open一個(gè)session,執(zhí)行多個(gè)HibernateTemplate的增刪改查方法就會(huì)創(chuàng)建多個(gè)session,請(qǐng)求過(guò)程中將會(huì)占用多個(gè)數(shù)據(jù)庫(kù)鏈接,并直到請(qǐng)求結(jié)束鏈接才會(huì)被釋放。

然后接著看HibernateTemplate的增刪改查代碼對(duì)session的處理情況,舉個(gè)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);
            }
        });
}

增刪改查都會(huì)調(diào)用executeWithNativeSession方法,并傳入一個(gè)HibernateCallback實(shí)例,執(zhí)行相應(yīng)的增刪改查操作。executeWithNativeSession方法屬于設(shè)計(jì)模式中的命令模式,簡(jiǎn)單來(lái)說(shuō)就是往這個(gè)方法傳入什么命令,hibernate就幫你執(zhí)行什么操作。繼續(xù)往里面跟蹤代碼可以看到:

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

這個(gè)方法就比較明了了:
首先獲取hibernate的session,就是上面分析過(guò)的那個(gè)方法。然后判斷existingTransaction,也就是當(dāng)前是否存在spring事務(wù)??梢钥吹絧reviousFlushMode = applyFlushMode(session, existingTransaction),這行代碼,如果當(dāng)前存在事務(wù),就會(huì)修改session的flushMode,并取得修改之前的previousFlushMode,執(zhí)行增刪改查操作之后再把這個(gè)previousFlushMode,也就是舊的flushMode設(shè)置回session中,這也就是為什么在單session情況下,有spring事務(wù)的可以執(zhí)行增刪改操作,而沒(méi)有spring事務(wù)執(zhí)行增刪改就會(huì)報(bào)錯(cuò)的原因。

中間的:

T result = action.doInHibernate(sessionToExpose);

此處就是執(zhí)行的增刪改操作,以及返回操作結(jié)果。hinernate創(chuàng)建session的時(shí)候并不會(huì)占用數(shù)據(jù)庫(kù)鏈接,只有hibernate真正執(zhí)行與數(shù)據(jù)庫(kù)交互的時(shí)候才會(huì)申請(qǐng)并占用數(shù)據(jù)庫(kù)鏈接。在執(zhí)行action.doInHibernate的時(shí)候hibernate其實(shí)還沒(méi)有開(kāi)始真正的進(jìn)行數(shù)據(jù)庫(kù)操作,只是在內(nèi)存中操作,只有執(zhí)行session.flush()的時(shí)候才會(huì)把內(nèi)存中的這些數(shù)據(jù)真正的同步到數(shù)據(jù)庫(kù)中,也就是執(zhí)行session.flush()的時(shí)候才會(huì)開(kāi)始占用數(shù)據(jù)庫(kù)鏈接。當(dāng)然查詢方法是個(gè)例外,因?yàn)楸仨氁獜臄?shù)據(jù)庫(kù)中取出數(shù)據(jù),所以如果是查詢方法,在查詢的時(shí)候就會(huì)與數(shù)據(jù)庫(kù)有交互操作,也就是進(jìn)行查詢的時(shí)候就會(huì)開(kāi)始占用數(shù)據(jù)庫(kù)鏈接。接下來(lái)的代碼就是判斷是否要進(jìn)行flush操作,繼續(xù)跟蹤到flushIfNecessary這個(gè)方法中:

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

可以看到,當(dāng)flushMode為FLUSH_EAGER的時(shí)候,或者不存在spring事務(wù)且flushMode不為FLUSH_NEVER的時(shí)候就會(huì)進(jìn)行session.flush()操作,同步數(shù)據(jù)。綜上可見(jiàn)在存在spring事務(wù)的情況下,所有dao的增刪改操作都不會(huì)馬上被同步到數(shù)據(jù)庫(kù)中,而要等到事務(wù)提交才會(huì)被flush到數(shù)據(jù)庫(kù)。所有如果你在某個(gè)帶有spring事務(wù)的service方法內(nèi)部對(duì)dao執(zhí)行try catch會(huì)出現(xiàn)捕獲不到數(shù)據(jù)庫(kù)異常的情況:

public calss Service{
    public void saveObj(){
        try{
            dao.save(dto);
        }catch(Exception e){
           //這里catch不到數(shù)據(jù)庫(kù)錯(cuò)誤
        }
    }
}

public calss Action{
    public void saveSomeThing(){
        try{
            service.saveObj(dto);
        }catch(Exception e){
           //這里才能catch到數(shù)據(jù)庫(kù)錯(cuò)誤
        }
    }
}

執(zhí)行完增刪改查之后,如果當(dāng)前有spring事務(wù)就把flushMode設(shè)置為原來(lái)的狀態(tài):session.setFlushMode(previousFlushMode)。如果當(dāng)前沒(méi)有spring事務(wù),會(huì)看到以下方法:

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

從這個(gè)方法就可以很清晰的看出在OpenSessionInViewFilter多session的情況下,只要deferredCloseHolder有holderMap,session就不會(huì)被關(guān)閉,而是被保存到Set<Session>集合中,也就是多session情況下數(shù)據(jù)庫(kù)鏈接在請(qǐng)求結(jié)束之前都不會(huì)被釋放的原因。

最后是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()
這行代碼是執(zhí)行攔截器鏈中的其他攔截器以及被攔截的目標(biāo)方法。這個(gè)方法前面的內(nèi)容就是開(kāi)啟事務(wù),后面的內(nèi)容就是提交和回滾事務(wù)。也就是spring聲明式事務(wù)可以不用手動(dòng)開(kāi)啟和提交事務(wù)的原理。createTransactionIfNecessary這個(gè)方法就是根據(jù)applicationcontext.xml的配置來(lái)生成不同類型的事務(wù),而commitTransactionAfterReturning就是提交事務(wù)。

跟蹤createTransactionIfNecessary進(jìn)入到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提供了多種事務(wù)嵌套的傳播機(jī)制,所以這段代碼里面有許多if else的判斷,開(kāi)啟事務(wù)在doBegin這個(gè)方法里面。這里需要注意最后那個(gè)else分支:創(chuàng)建一個(gè)空的事務(wù),并非真正的數(shù)據(jù)庫(kù)事務(wù),也就是不執(zhí)行session.begintransaction()這個(gè)開(kāi)啟事務(wù)的方法。spring事務(wù)傳播有個(gè)配置叫PROPAGATION_NOT_SUPPORTED:意思是以非事務(wù)方式執(zhí)行操作,如果當(dāng)前存在事務(wù),就把當(dāng)前事務(wù)掛起。PROPAGATION_NOT_SUPPORTED這個(gè)配置就會(huì)走到最后的這個(gè)else分支。雖然以非事務(wù)方式運(yùn)行,但是在前面分析的許多用來(lái)判斷當(dāng)前是否存在spring事務(wù)的existingTransaction這個(gè)變量的值依舊會(huì)是ture。也就是說(shuō),spring認(rèn)為當(dāng)前存在事務(wù),雖然是一個(gè)空事務(wù)。這就導(dǎo)致可能出現(xiàn)下面的情況:

  • 在某個(gè)service中配置為PROPAGATION_NOT_SUPPORTED的方法中開(kāi)始執(zhí)行了查數(shù)據(jù)庫(kù)的操作,占用了一個(gè)數(shù)據(jù)庫(kù)鏈接,然后又把查詢出來(lái)的數(shù)據(jù)作為參數(shù)調(diào)用了一個(gè)耗時(shí)很長(zhǎng)的外圍系統(tǒng)的接口,這樣就會(huì)導(dǎo)致這個(gè)空事務(wù)一直不被提交,這個(gè)數(shù)據(jù)庫(kù)鏈接也就會(huì)被一直被占用而無(wú)法釋放。

繼續(xù)看開(kāi)啟事務(wù)的doBegin方法:

doBegin

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

執(zhí)行完目標(biāo)方法,中間有一個(gè)準(zhǔn)備提交事務(wù)的方法:

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()方法,也就是前面說(shuō)的事務(wù)開(kāi)始提交的時(shí)候才會(huì)把hibernate內(nèi)存中的數(shù)據(jù)同步到數(shù)據(jù)庫(kù)之中。

最后就是事務(wù)提交完成之后的處理session.disconnect()或者session.close()釋放數(shù)據(jù)庫(kù)鏈接,如下:

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的情況下,如果沒(méi)有嵌套事務(wù),txObject.isNewSessionHolder()和txObject.isNewSession()的返回值為true,會(huì)執(zhí)行unbindResource操作和closeSessionOrRegisterDeferredClose操作。
  • 單session情況下不解除綁定,后面依然可以從ThreadLocal中獲取到session,但是會(huì)執(zhí)行session.disconnect()來(lái)關(guān)閉數(shù)據(jù)庫(kù)鏈接。

下面給出各種情況下的結(jié)論:

  1. 在單session情況下,session會(huì)被綁定至當(dāng)前線程(ThreadLocal),整個(gè)請(qǐng)求過(guò)程中共用一個(gè)sesssion,整個(gè)請(qǐng)求過(guò)程至多一個(gè)數(shù)據(jù)庫(kù)連接。如果執(zhí)行帶有spring事務(wù)的service方法結(jié)束之后會(huì)觸發(fā)session.disconnect()釋放連接。雖然只有一個(gè)連接,但如果在service內(nèi)部方法結(jié)束之前調(diào)用了外圍耗時(shí)的操作會(huì)導(dǎo)致連接長(zhǎng)時(shí)間不被釋放。

  2. 多session情況下,每執(zhí)行一個(gè)dao或者service方法都將創(chuàng)建一個(gè)session,每個(gè)session都將占用一個(gè)數(shù)據(jù)庫(kù)連接,dao和service方法結(jié)束都不會(huì)關(guān)閉session,直到請(qǐng)求結(jié)束才會(huì)釋放連接。service內(nèi)部的所有dao操作共用一個(gè)sesison。如果在for循環(huán)中調(diào)用dao或者service,將產(chǎn)生n個(gè)連接。所以,這種模式下一次請(qǐng)求將占用n個(gè)連接,將會(huì)導(dǎo)致數(shù)據(jù)庫(kù)連接瞬間飆升的情況。

  3. 后臺(tái)自啟線程,如quartz定時(shí)調(diào)度等不經(jīng)過(guò)OpenSessionInViewFilter的情況下,每執(zhí)行一個(gè)dao或者service方法都將創(chuàng)建一個(gè)session,執(zhí)行結(jié)束之后都會(huì)關(guān)閉sessoin。service內(nèi)部的所有dao操作共用一個(gè)sesison。所以即使在for循環(huán)里面調(diào)用dao或者service,同一時(shí)刻也只會(huì)占用一個(gè)數(shù)據(jù)庫(kù)連接,不會(huì)導(dǎo)致數(shù)據(jù)庫(kù)連接飆升的情況。

  4. 創(chuàng)建hibernate的sessoin的時(shí)候并不占用數(shù)據(jù)庫(kù)連接,執(zhí)行查詢方法或者session的flush方法才會(huì)開(kāi)始占用數(shù)據(jù)庫(kù)連接。spring事務(wù)開(kāi)啟的時(shí)候,由于需要調(diào)用jdbc底層的Connection.setAutoCommit方法,所以spring事務(wù)開(kāi)啟的時(shí)候,即使中間沒(méi)有執(zhí)行任何數(shù)據(jù)庫(kù)操作,也會(huì)占用數(shù)據(jù)庫(kù)連接。如果service方法的propagation為PROPAGATION_NOT_SUPPORTED,雖有spring事務(wù)(空事務(wù)),但是由于不會(huì)創(chuàng)建JDBC底層事務(wù),所以這類的service方法開(kāi)始的時(shí)候不會(huì)占用數(shù)據(jù)庫(kù)連接。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,578評(píng)論 19 139
  • 這部分主要是開(kāi)源Java EE框架方面的內(nèi)容,包括Hibernate、MyBatis、Spring、Spring ...
    雜貨鋪老板閱讀 1,569評(píng)論 0 2
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,275評(píng)論 6 342
  • 人之初,性本善,這是一種認(rèn)識(shí),對(duì)錯(cuò)各半! 嬰兒生來(lái)不知善惡,不斷長(zhǎng)大會(huì)受父母的耳濡目染,外在環(huán)境更是會(huì)給孩子帶來(lái)重...
    小小星火閱讀 326評(píng)論 0 0
  • 今天的事情太多,讓我本來(lái)打算休養(yǎng)生息的計(jì)劃徹底擱淺,不得已只能在外面奔波了一整天。從參加同事女兒的滿月酒,到感到大...
    夏野閱讀 231評(píng)論 0 0

友情鏈接更多精彩內(nèi)容