• 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏吧

数据库之SqlSessionTemplate源码解析

互联网 diligentman 2周前 (01-12) 12次浏览

前言

在普遍的JAVA-WEB项目的实际业务处理中,最终都是通过SqlSessionTemplate执行数据库的CURD操作。本文结合mybatis源码,对SqlSessionTemplate进行详细的介绍。

SqlSessionTemplate是个线称安全的类,如果你的系统是微服务架构的话,那一个微服务(组件)里面的所有DAO可以共享同一个SqlSessionTemplate对应的bean实例。因为SqlSessionTemplate实现了SqlSession接口,他会保证使用的SqlSession是和当前Spring的事务相关的,并且他还会管理session的生命周期,包含关闭、提交和回滚操作。

那他具体是如何实现的呢,下面进行源码解析。


一、SqlSessionTemplate的初始化

首先,SqlSessionTemplate在进行初始化的时候,会把SqlSessionFactory作为参数传入:

<bean id="mySqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate"> 
  <constructor-arg index="0" ref="sqlSessionFactory"/> 
</bean> 

二、创建SqlSessionFactory的代理类

此处,在初始化SqlSessionTemplate实例的时候,创建一个SqlSessionFactory的代理类作为SqlSessionTemplate的一个属性是关键点。

以下均为mysql源码

1.创建代理

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    // 此处创建了sqlSession的代理类的实例,当通过代理类进行方法调用时
    // 该调用会被导向SqlSessionInterceptor的invoke方法
    this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class }, new SqlSessionInterceptor());
  }

2.切面实现

创建代理的目的就是执行切面方法,下面进行切面功能的详细解读:

private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) 
            throws Throwable {
        // 获取SqlSession(这个SqlSession才是真正使用的,它是线程独有的)
        // 对于getSqlSession方法的具体实现,在下面有详细解读
        SqlSession sqlSession = getSqlSession(
                SqlSessionTemplate.this.sqlSessionFactory,
                SqlSessionTemplate.this.executorType,
                SqlSessionTemplate.this.exceptionTranslator);
        try {
            // 调用从Spring的事物上下文中获取的sqlSession
            // 结合具体的参数(args)和sqlSession,进行最终的sql执行,操作数据库
            Object result = method.invoke(sqlSession, args);
            // 然后判断一下当前的sqlSession是否被Spring托管 如果未被Spring托管则自动commit
            if (!isSqlSessionTransactional(sqlSession, 
                    SqlSessionTemplate.this.sqlSessionFactory)) {
                // force commit even on non-dirty sessions because some databases require
                // a commit/rollback before calling close()
                sqlSession.commit(true);
            }
            return result;
        } catch (Throwable t) {
            // 如果出现异常则根据情况转换后抛出
            Throwable unwrapped = unwrapThrowable(t);
            if (SqlSessionTemplate.this.exceptionTranslator != null && 
                    unwrapped instanceof PersistenceException) {
                // release the connection to avoid a deadlock if the 
                // translator is no loaded. See issue #22
                closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                sqlSession = null;
                Throwable translated = SqlSessionTemplate.this.exceptionTranslator.
                        translateExceptionIfPossible((PersistenceException) unwrapped);
                if (translated != null) {
                    unwrapped = translated;
                }
            }
            throw unwrapped;
        } finally {
            if (sqlSession != null) {
                // 关闭sqlSession的具体实现,是根据当前sqlSession是否在Spring的事物上下文中来决定具体操作的
                // 如果sqlSession被Spring管理,则调用holder.released(),使计数器-1
                // 否则直接关闭当前的sqlSession
                closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
            }
        }
    }
}

getSqlSession方法如下:

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, 
        ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);

    // 根据sqlSessionFactory,从TransactionSynchronizationManager定义的资源map
    // 获取当前线程对应的SqlSessionHolder
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.
            getResource(sessionFactory);

    // 从SqlSessionHolder中提取SqlSession对象
    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
        return session;
    }

    if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Creating a new SqlSession");
    }
    // 如果无法通过SqlSessionHolder获取到sqlSession,那就利用sessionFactory新创建一个sqlSession
    session = sessionFactory.openSession(executorType);

    // 新创建一个SqlSessionHolder,并且将刚创建的sqlSession与其绑定
    // 然后将SqlSessionHolder对象注册到TransactionSynchronizationManager中
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
}

总结

综上所述,通过对SqlSessionTemplate的源码解读,我们应该可以理解为什么可以在一个微服务(组件)里面的所有DAO可以共享同一个SqlSessionTemplate对应的bean实例,并保证线程安全和事务的完整性,因为:

1、多个线程都通过调用同一个SqlSessionTemplate进行数据库操作时,SqlSessionTemplate通过代理机制,每一次都执行切面里的getSqlSession方法来获取真正操作数据库的sqlSession。

2、在getSqlSession方法中,使用到了重要的TransactionSynchronizationManager类,此类利用ThreadLocal方式,将当前线程与一个事物相绑定,从而在当前线程想要获取sqlSession时,判断当前线程是否存在还未完成的事物,如果存在则利用与当前事物绑定的sqlSession执行数据库操作,如果不存在则新建sqlSession进行相应操作,从而保证了线程的安全和数据库事物的完整性。


喜欢 (0)