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

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