树,求扎深根。人,求知深理。
先定义两类Mapper
1.通过@AutoWire自动注入的Mapper,在Service实现中常用。
2.通过自己打开SqlSession并且通过SqlSession.getMapper方法获得的Mapper
先摆出结论:
第一类Mapper在每次使用完CRUD之类的方法(insertByExample等)后会自动调用SqlSession的close方法,调用SqlSession对应的Connection(如果使用了连接池一般会把调用close的Connection放到连接池中复用)。
第二类Mapper每次调用完方法后不会自动关闭SqlSession!如果一直打开SqlSession并且获得Mapper但是使用完后不关闭SqlSession的话会导致数据库连接占用,以后的连接获取请求可能都会超时失败!
探究
第一类Mapper是通过xml文件配置的[Mapper扫描配置Bean]扫描创建的。XML配置如下,这个xml是applicationContext.xml,是spring容器的一个Bean配置
来到源码。MapperScannerConfigurer的postProcessBeanDefinitionRegistry方法下。从名字上看大体是一个用来注册Bean定义的方法。从源码上看应该是扫描对应的包路径,然后将扫描到的类用来创建Bean并且注册Bean,这里的Bean是Mapper(实际上是代理实现)。
分析ClassPathMapperScanner的scan方法,scan方法直接调用了doScan方法,过程很短,不贴scan方法的图了。
ClassPathMapperScanner的doScan的重点部分。GenericBeanDefinition明确地把Bean的所属类设置成了MapperFactoryBean!说明返回的Bean和这个类或多或少有关系。
MapperFactoryBean继承了SqlSessionDaoSupport
SqlSessionDaoSupport
可以看到这个类中的SqlSession统一使用了SqlSessionTemplate。而这个类是今天的主角。SqlSessionTemplate(实现了SqlSession)代替了SqlSession给Mapper使用。
而如下图,我们可以看到他其实是在内部代理了一个SqlSession,这个SqlSession用JDK的动态代理“裹了”一层SqlSessionInterceptor
其实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方法。下面是第二类Mapper的调用堆栈,会发现是直接调用了DefaultSqlSession的方法。并没有关闭SqlSession。
上面的Bean加载过程复杂,笔者暂时没有研究。如果有兴趣的小伙伴可以去阅读org.mybatis.spring.mapper包下相关的源码。
通过这次研究,我更倾向于使用@AutoWire以Bean形式使用的Mapper,因为这样的Mapper能够在方法执行完后自动关闭SqlSession。
(待补充)