Mybatis: 关于@Autowire得到的Mapper 和 手动SqlSession.getMapper获得的Mapper 使用后SqlSession关闭问题

树,求扎深根。人,求知深理。

先定义两类Mapper

1.通过@AutoWire自动注入的Mapper,在Service实现中常用。
Mybatis: 关于@Autowire得到的Mapper 和 手动SqlSession.getMapper获得的Mapper 使用后SqlSession关闭问题_第1张图片

2.通过自己打开SqlSession并且通过SqlSession.getMapper方法获得的Mapper

Mybatis: 关于@Autowire得到的Mapper 和 手动SqlSession.getMapper获得的Mapper 使用后SqlSession关闭问题_第2张图片先摆出结论:
第一类Mapper在每次使用完CRUD之类的方法(insertByExample等)后会自动调用SqlSession的close方法,调用SqlSession对应的Connection(如果使用了连接池一般会把调用close的Connection放到连接池中复用)。

第二类Mapper每次调用完方法后不会自动关闭SqlSession!如果一直打开SqlSession并且获得Mapper但是使用完后不关闭SqlSession的话会导致数据库连接占用,以后的连接获取请求可能都会超时失败!

探究
第一类Mapper是通过xml文件配置的[Mapper扫描配置Bean]扫描创建的。XML配置如下,这个xml是applicationContext.xml,是spring容器的一个Bean配置Mybatis: 关于@Autowire得到的Mapper 和 手动SqlSession.getMapper获得的Mapper 使用后SqlSession关闭问题_第3张图片

来到源码。MapperScannerConfigurer的postProcessBeanDefinitionRegistry方法下。从名字上看大体是一个用来注册Bean定义的方法。从源码上看应该是扫描对应的包路径,然后将扫描到的类用来创建Bean并且注册Bean,这里的Bean是Mapper(实际上是代理实现)。

Mybatis: 关于@Autowire得到的Mapper 和 手动SqlSession.getMapper获得的Mapper 使用后SqlSession关闭问题_第4张图片

分析ClassPathMapperScanner的scan方法,scan方法直接调用了doScan方法,过程很短,不贴scan方法的图了。
ClassPathMapperScanner的doScan的重点部分。GenericBeanDefinition明确地把Bean的所属类设置成了MapperFactoryBean!说明返回的Bean和这个类或多或少有关系。

Mybatis: 关于@Autowire得到的Mapper 和 手动SqlSession.getMapper获得的Mapper 使用后SqlSession关闭问题_第5张图片
MapperFactoryBean继承了SqlSessionDaoSupport
继承关系
SqlSessionDaoSupport
Mybatis: 关于@Autowire得到的Mapper 和 手动SqlSession.getMapper获得的Mapper 使用后SqlSession关闭问题_第6张图片可以看到这个类中的SqlSession统一使用了SqlSessionTemplate。而这个类是今天的主角。SqlSessionTemplate(实现了SqlSession)代替了SqlSession给Mapper使用。

而如下图,我们可以看到他其实是在内部代理了一个SqlSession,这个SqlSession用JDK的动态代理“裹了”一层SqlSessionInterceptor

template

其实SqlSessionTemplate代理的是一个空SqlSession,每次调用这个SqlSession的方法都会调用下面的invoke方法代替。在下面的SqlSessionInterceptor的invoke方法中,每次都会创建一个新的SqlSession。并且在CRUD操作完成后,在finally块中关闭这个SqlSession。所以说Mybatis用Bean形式生成的Mapper是会在每次调用方法后自动关闭对应的SqlSession的!

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);

    Object unwrapped;
    try {
        Object result = method.invoke(sqlSession, args);
        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) {
            SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }

    }

    return unwrapped;
}

从调用堆栈也可以看出来,这是第一种Mapper的调用堆栈。
可以看出中途调用了代理类SqlSessionTemplate的selectList方法和它的内部类SqlSessionInterceptor的invoke方法。Mybatis: 关于@Autowire得到的Mapper 和 手动SqlSession.getMapper获得的Mapper 使用后SqlSession关闭问题_第7张图片下面是第二类Mapper的调用堆栈,会发现是直接调用了DefaultSqlSession的方法。并没有关闭SqlSession。

Mybatis: 关于@Autowire得到的Mapper 和 手动SqlSession.getMapper获得的Mapper 使用后SqlSession关闭问题_第8张图片

上面的Bean加载过程复杂,笔者暂时没有研究。如果有兴趣的小伙伴可以去阅读org.mybatis.spring.mapper包下相关的源码。
通过这次研究,我更倾向于使用@AutoWire以Bean形式使用的Mapper,因为这样的Mapper能够在方法执行完后自动关闭SqlSession。
(待补充)

你可能感兴趣的:(JAVA)