Mybatis: 關於@Autowire得到的Mapper 和 手動SqlSession.getMapper獲得的Mapper 使用後SqlSession關閉問題

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

先定義兩類Mapper

1.通過@AutoWire自動注入的Mapper,在Service實現中常用。
第一類Mapper

2.通過自己打開SqlSession並且通過SqlSession.getMapper方法獲得的Mapper

第二類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配置Mapper掃描配置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

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方法。在這裏插入圖片描述下面是第二類Mapper的調用堆棧,會發現是直接調用了DefaultSqlSession的方法。並沒有關閉SqlSession。

在這裏插入圖片描述

上面的Bean加載過程複雜,筆者暫時沒有研究。如果有興趣的小夥伴可以去閱讀org.mybatis.spring.mapper包下相關的源碼。
通過這次研究,我更傾向於使用@AutoWire以Bean形式使用的Mapper,因爲這樣的Mapper能夠在方法執行完後自動關閉SqlSession。
(待補充)

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