SqlSessionTemplate爲什麼線程安全

最近在看Mybatis源碼,對於理解SqlSessionTemplate是如何保證線程安全的網上的文章不多。希望通過本文能夠幫助大家清楚理解,類關係圖如下:

DefaultSqlSession與SqlSessionManager解析

在Mybatis中SqlSession默認有DefaultSqlSession和SqlSessionManager兩個實現類

DefaultSqlSession是真正的實現類調用Executor,但不是線程安全的。

Mybatis又實現了對SqlSession和SQLSessionFactory的封裝類SqlSessionManager,線程安全並通過localSqlSession實現複用從而提高性能。

private ThreadLocal<SqlSession> localSqlSession = new ThreadLocal();

SqlSessionManager通過SqlSessionInterceptor實現對DefaultSqlSession代理調用。

private class SqlSessionInterceptor implements InvocationHandler {
        public SqlSessionInterceptor() {
        }

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //由調用者決定當前線程是否複用 SqlSession 
            SqlSession sqlSession = (SqlSession)SqlSessionManager.this.localSqlSession.get();
            if (sqlSession != null) {
                try {
                    return method.invoke(sqlSession, args);
                } catch (Throwable var12) {
                    throw ExceptionUtil.unwrapThrowable(var12);
                }
            } else {
                //如果不復用,則每次調用都新建 SqlSession 並使用後銷燬
                SqlSession autoSqlSession = SqlSessionManager.this.openSession();

                Object var7;
                try {
                    Object result = method.invoke(autoSqlSession, args);
                    autoSqlSession.commit();
                    var7 = result;
                } catch (Throwable var13) {
                    autoSqlSession.rollback();
                    throw ExceptionUtil.unwrapThrowable(var13);
                } finally {
                    autoSqlSession.close();
                }

                return var7;
            }
        }
    }

DefaultSqlSession和SqlSessionManager之間的區別:

1、單例模式下DefaultSqlSession是線程不安全的,而SqlSessionManager是線程安全的;

2、SqlSessionManager可以選擇通過localSqlSession這個ThreadLocal變量,記錄與當前線程綁定的SqlSession對象,供當前線程循環使用,從而避免在同一個線程多次創建SqlSession對象造成的性能損耗;

3、使用DefaultSqlSession爲了保證線程安全需要爲每一個操作都創建一個SqlSession對象,其性能可想而知;

SqlSessionTemplate是怎麼保證線程安全

SqlSessionTemplate是MyBatis專門爲Spring提供的,支持Spring框架的一個SqlSession獲取接口。主要是爲了繼承Spring,並同時將是否共用SqlSession的權限交給Spring去管理。

1、通過創建sqlSessionProxy代理類,將調用導向SqlSessionInterceptor的invoke方法。

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
        Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
        Assert.notNull(executorType, "Property 'executorType' is required");
        this.sqlSessionFactory = sqlSessionFactory;
        this.executorType = executorType;
        this.exceptionTranslator = exceptionTranslator;
        this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
    }

2、獲取線程私有的SqlSession,調用DefaultSqlSession對應的實際方法上

private class SqlSessionInterceptor implements InvocationHandler {
        private SqlSessionInterceptor() {
        }

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //獲取同一個線程的複用sqlSession,如果沒有則新生成一個並存到線程私有存儲中
            SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);

            Object unwrapped;
            try {
                //實際調用DefaultSession的對應方法
                Object result = method.invoke(sqlSession, args);
                //判斷當前sqlSession是否被Spring管理,如果沒有直接commit
                if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
                    sqlSession.commit(true);
                }

                unwrapped = result;
            } catch (Throwable var11) {
                unwrapped = ExceptionUtil.unwrapThrowable(var11);
                if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
                    SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                    sqlSession = null;
                    Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
                    if (translated != null) {
                        unwrapped = translated;
                    }
                }

                throw (Throwable)unwrapped;
            } finally {
                if (sqlSession != null) {
                    //正常返回將線程私有sqlSession調用次數減一
                    SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                }

            }

            return unwrapped;
        }
    }

3、查看getSqlSession()方法就知道每個線程對應的SqlSession都是私有的不會被共用,所以SqlSessionTemplate是線程安全的。

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
        Assert.notNull(sessionFactory, "No SqlSessionFactory specified");
        Assert.notNull(executorType, "No ExecutorType specified");
        //從線程私有存儲中獲取SqlSession
        SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
        SqlSession session = sessionHolder(executorType, holder);
        if (session != null) {
            return session;
        } else {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Creating a new SqlSession");
            }
            //沒有則新建一個DefaultSqlSession
            session = sessionFactory.openSession(executorType);
            //存到線程私有存儲中
            registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
            return session;
        }
    }

總結

究其根本SqlSession真正的實現類只有DefaultSqlSession,SqlSessionManager和SqlSessionTemplate都是通過代理轉發到DefaultSqlSession對應方法。

單例模式下的DefaultSqlSession不是線程安全的,SqlSessionManager和SqlSessionTemplate線程安全的根本就是每一個線程對應的SqlSession都是不同的。如果每一個操作都創建一個SqlSession對象,操作完又進行銷燬導致性能極差。通過線程私有ThreadLocal存儲SqlSession進行復用,從而提高性能。

參考:https://blog.csdn.net/bntx2jsqfehy7/article/details/79441545#commentsedit

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章