MyBatisPlus(1)-注入SqlSession


一、前言

  • SpringBoot 结合 MyBatisPlus 是如何注入 SqlSession 的?
  • SqlSession 如何实现在相同请求是相同对象,不同请求中是不同对象?

二、注入的 SqlSession

@Autowired
private SqlSession sqlSession;

@GetMapping("/test")
public void test(){
    log.info(sqlSession.toString());
    log.info(sqlSession.toString());
}

通过上面的代码可以发现:每次请求中打印的 sqlSession 都是同一个对象,但是为什么又说 sqlSession 的生命周期是一个会话(请求)?

1、其实通过断点可以发现注入的 sqlSession 的类型是 SqlSessionTemplate,而不是我们熟知的 DefaultSqlSessionsqlSession 自动注入源码如下:

@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisPlusProperties.class)
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisPlusLanguageDriverAutoConfiguration.class})
public class MybatisPlusAutoConfiguration implements InitializingBean {
    // ...

    /**
    * 当容器中不存在 SqlSessionTemplate 类型的 Bean 的时候
    * 创建一个 SqlSessionTemplate 对象并注入
    */ 
    @Bean
    @ConditionalOnMissingBean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        ExecutorType executorType = this.properties.getExecutorType();
        if (executorType != null) {
            return new SqlSessionTemplate(sqlSessionFactory, executorType);
        } else {
            return new SqlSessionTemplate(sqlSessionFactory);
        }
    }

}

关于 SqlSessionTemplate 官方解释如下:

线程安全的,Spring管理的,与Spring事务管理一起工作的SqlSession,以确保实际使用的SqlSession 与当前Spring事务相关联。此外,它还管理会话生命周期,包括根据Spring事务配置根据需要关闭、提交或回滚会话。

模板需要一个SqlSessionFactory来创建SqlSessions,作为构造函数参数传递。它也可以被构造为指示要使用的执行程序类型,如果不是,则将使用会话工厂中定义的默认执行程序类型。

2、通过查看 SqlSessionTemplate 的成员属性可以发现下面这个字段 sqlSessionProxy

/**
* SqlSessionTemplate 是典型的装饰者模式:
* 在不改变现有对象结构的情况下,动态地给该对象增加一些其额外功能的模式。
*/
public class SqlSessionTemplate implements SqlSession, DisposableBean {

    //实际工作的 sqlSession 代理类,类型是 DefaultSqlSession
    private final SqlSession sqlSessionProxy;

    @Override
    public int insert(String statement) {
        return this.sqlSessionProxy.insert(statement);
    }
}

通过断点多次请求可以发现,注入的 SqlSession (类型是 SqlSessionTemplate)是同一个对象,其中的 sqlSessionProxy 属性也是同一个的对象。

从上面的 insert 方法中可以看出实际走的是 sqlSessionProxy的代理方法

sqlSessionProxy属性的赋值是在 SqlSessionTemplate 的构造器中

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;

    // jdk 动态代理
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class }, 
        new SqlSessionInterceptor()
    );
}

此时还不能解决我们的疑问:多次请求注入的同一 sqlSessionTemplatesqlSessionProxy 也是相同的,但每次请求实际调用 insert 的对象不应该是同一个?

此时就可以将目光转向 JDK 动态代理的调用处理器 SqlSessionInterceptor

SqlSessionInterceptorsqlSessionTemplate 的子类

public class SqlSessionTemplate implements SqlSession, DisposableBean {

    private class SqlSessionInterceptor implements InvocationHandler {

        /**
        * sqlSessionProxy 每次执行 SqlSession 接口的方法,都是执行下面的代理增强方法
        * proxy即sqlSessionProxy,但此处没有用到
        */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

            // 重点:获取 sqlSession 对象
            SqlSession sqlSession = getSqlSession(
                SqlSessionTemplate.this.sqlSessionFactory,
                SqlSessionTemplate.this.executorType, 
                SqlSessionTemplate.this.exceptionTranslator
            );

            try {
                // 每次执行都是使用上面方法返回的对象的方法
                Object result = method.invoke(sqlSession, args);

                if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
                    // 由于一些数据库的需要强制提交事务
                    sqlSession.commit(true);
                }
                return result;
            } catch (Throwable t) {
                //...
            } finally {
                //...
            }
        }
    }
}

在此其实不难猜出 getSqlSession() 方法的主要作用是获取当前线程(请求)所属的 sqlSession ,在这也可以确定 sqlSession 其实是放在 ThreadLocal

/**
* 从Spring事务管理器中获取一个SqlSession,或者根据需要创建一个新的。
* 尝试从当前事务中获取SqlSession。如果没有,则创建一个新的。
* 然后,如果Spring TX是开启的,并且SpringManagedTransactionFactory
* 被配置为事务管理器,它就会将SqlSession与事务同步。
*/
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, 
                                       ExecutorType executorType,
                                       PersistenceExceptionTranslator exceptionTranslator) {

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

    // 从Spring事务管理器中获取当前线程的SqlSessionHolder
    // SqlSessionHolder中成员属性存放了SqlSession
    // 见下一段代码
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    // 其实就是一个get方法session = holder.getSqlSession();
    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
        return session;
    }

    // 如果当前线程没有 SqlSession ,则创建一个
    LOGGER.debug(() -> "Creating a new SqlSession");
    session = sessionFactory.openSession(executorType);

    // 储存创建的 SqlSession 
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
}

**获取SqlSessionHolder **

TransactionSynchronizationManager 获取 SqlSessionHolder

/**
* TransactionSynchronizationManager 线程资源管理器
* 管理每个线程的资源和事务同步的中央委托。
*/
public abstract class TransactionSynchronizationManager {

    // -------------- 资源存储 -----------------

    // 事务资源
    private static final ThreadLocal<Map<Object, Object>> resources =
        new NamedThreadLocal<>("Transactional resources");

    private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
        new NamedThreadLocal<>("Transaction synchronizations");

    // 当前事务名
    private static final ThreadLocal<String> currentTransactionName =
        new NamedThreadLocal<>("Current transaction name");

    // 当前事务是否只读
    private static final ThreadLocal<Boolean> currentTransactionReadOnly =
        new NamedThreadLocal<>("Current transaction read-only status");

    // 当前事务的隔离级别
    private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
        new NamedThreadLocal<>("Current transaction isolation level");

    // 当前事务的活动状态
    private static final ThreadLocal<Boolean> actualTransactionActive =
        new NamedThreadLocal<>("Actual transaction active");


    // -------------- 获取资源方法 ---------------------
    @Nullable
    public static Object getResource(Object key) {
        Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
        Object value = doGetResource(actualKey);

        // 日志 ...
        return value;
    }

    @Nullable
    private static Object doGetResource(Object actualKey) {
        Map<Object, Object> map = resources.get();
        if (map == null) {
            return null;
        }
        Object value = map.get(actualKey);

        // 删除 值为空的数据
        if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
            map.remove(actualKey);
            // Remove entire ThreadLocal if empty...
            if (map.isEmpty()) {
                resources.remove();
            }
            value = null;
        }
        return value;
    }
}

  目录