Mybatis-Spring:SqlSessionTemplate

環(huán)境:mybatis-spring 2.0.3

Mybatis提供了一個工具類SqlSessionTemplate,通過它可以操作所有Mapper配置文件的SQL語句

@Test
public void testInsert() {
    Product product = (Product) super.context.getBean("product");
    // Mapper接口全限定名稱+Sql語句標簽的Id
    String sql = "com.hyc.dao.ProductMapper.insertProduct";
    int add = super.template.insert(sql, product);
    System.out.println(add > 0 ? "插入成功" : "插入失敗");
}

打開SqlSessionTemplate源碼,根據(jù)注釋內(nèi)容,它是由Spring管理的線程安全類,與Spring的事務(wù)管理器協(xié)作確保真實的SqlSession被關(guān)聯(lián)到當前的Spring事務(wù)。更進一步,它基于Spring事務(wù)配置管理session的生命周期,包括關(guān)閉、提交、回滾

同時,因為是線程安全的,所以可以作為單例對象被所有的Dao使用

public class SqlSessionTemplate implements SqlSession, DisposableBean {

    private final SqlSessionFactory sqlSessionFactory;
    private final ExecutorType executorType;
    // SqlSession代理,通過它執(zhí)行數(shù)據(jù)庫操作
    // 通過這個代理對象,可以獲取到專屬于當前線程的SqlSession對象
    private final SqlSession sqlSessionProxy;  

    // 構(gòu)造器
    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, 
            ExecutorType executorType, 
            PersistenceExceptionTranslator exceptionTranslator) {

        // ...

        this.sqlSessionFactory = sqlSessionFactory;
        this.executorType = executorType;
        this.exceptionTranslator = exceptionTranslator;
        // 使用JDK動態(tài)代理創(chuàng)建一個SqlSession代理對象
        this.sqlSessionProxy = (SqlSession) newProxyInstance(
                                                SqlSessionFactory.class.getClassLoader(),
                                                new Class[] { SqlSession.class },
                                                new SqlSessionInterceptor());
    }

    // 原生mybatis采用DefaultSqlSession發(fā)起數(shù)據(jù)庫操作
    // mybatis-spring則通過一個代理對象,它代理的是原生DefaultSqlSession
    // 之所以使用代理,是為了獲取當前線程專屬的DefaultSqlSession
    public <T> T selectOne(String statement, Object parameter) {
        return this.sqlSessionProxy.selectOne(statement, parameter);
    }
}

根據(jù)上述代碼段,可以知道SqlSessionTemplate也實現(xiàn)了SqlSession接口,同時組合了另一個SqlSession接口代理對象sqlSessionProxy執(zhí)行具體的數(shù)據(jù)庫操作,代理對象的增強邏輯如下

 private class SqlSessionInterceptor implements InvocationHandler {
    @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 {
            // 通過反射調(diào)用目標對象
            Object result = method.invoke(sqlSession, args);
            if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
                sqlSession.commit(true);
            }
            return result;
        } catch (Throwable t) {
            Throwable unwrapped = unwrapThrowable(t);
            if (SqlSessionTemplate.this.exceptionTranslator != null 
                    && unwrapped instanceof PersistenceException) {
                // 關(guān)閉SqlSession
                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) {
                // 關(guān)閉SqlSession
                closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
            }
        }
    }
  }

SqlSessionInterceptor中可以看到,代理對象還是先獲取了一個SqlSession對象

SqlSession sqlSession = getSqlSession(
                SqlSessionTemplate.this.sqlSessionFactory,
                SqlSessionTemplate.this.executorType,
                SqlSessionTemplate.this.exceptionTranslator);

SqlSessionTemplate作為一個全局單例的Bean,必然在多個Dao層中共同使用,那么不可避免會遇到Servelt容器的多線程環(huán)境所導致的線程安全問題,對此,它通過SqlSessionUtils解決了線程安全的問題

public final class SqlSessionUtils {

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

        // ...

        // SqlSessionTemplate的線程安全性由此保證
        // TransactionSynchronizationManager保證了線程安全
        // 追根揭底,這個類采用的技術(shù)還是ThreadLocal線程隔離技術(shù)
        SqlSessionHolder holder = (SqlSessionHolder) 
                TransactionSynchronizationManager.getResource(sessionFactory);

        SqlSession session = sessionHolder(executorType, holder);
        if (session != null) {
            return session;
        }

        // ...
        // 如果當前線程沒有session,則通過SessionFactory創(chuàng)建
        // 這里就又回到了mybatis的DefaultSessionFactory,
        // 創(chuàng)建Mybatis原生的DefaultSqlSession
        session = sessionFactory.openSession(executorType);
        // 注冊上面創(chuàng)建的session對象
        registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
        return session;
    }

    // 使用SqlSessionHolder封裝SqlSession,保存到ThreadLocal對象中
    private static void registerSessionHolder(SqlSessionFactory sessionFactory, 
            ExecutorType executorType, 
            PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
        SqlSessionHolder holder;
        // ...
        holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
        // 將當前的SqlSession保存到ThreadLocal
        TransactionSynchronizationManager.bindResource(sessionFactory, holder);
        TransactionSynchronizationManager.registerSynchronization(
                new SqlSessionSynchronization(holder, sessionFactory));
        holder.setSynchronizedWithTransaction(true);
        holder.requested();
        // ...
    }
}

Spring提供了TransactionSynchronizationManager管理每個線程的資源和事務(wù)的同步狀態(tài),它采用了ThreadLocal線程隔離保證線程安全性

public abstract class TransactionSynchronizationManager {

    // mybatis使用它保存當前線程信息 
    // Key為SqlSessionFactory value為SqlSessionHolder
    private static final ThreadLocal<Map<Object, Object>> resources =
            new NamedThreadLocal<>("Transactional resources");

    // 獲取當前多線程專屬的SqlSessionHolder
    public static Object getResource(Object key) {
        Object actualKey  = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
        Object value = doGetResource(actualKey);
        // ...
        return value;
    }

    // 從ThreadLocal獲取SqlSessionHolder
    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;
    }
    
    // mybatis應(yīng)用這個方法將SqlSessionHolder保存到ThreadLocal
    public static void bindResource(Object key, Object value) 
            throws IllegalStateException {
        Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
        Map<Object, Object> map = resources.get();
        // set ThreadLocal Map if none found
        if (map == null) {
            map = new HashMap<>();
            // 保存到ThreadLocal
            resources.set(map);
        }
        // ...
    }
}

繼續(xù)回到SqlSessionInterceptor,獲取到當前線程專屬的SqlSession之后,通過它處理完畢之后會關(guān)閉當前SqlSession,這也是Mybatis集成Spring之后一級緩存失效的原因

// 通過反射執(zhí)行DefaultSqlSession對象的同名方法
Object result = method.invoke(sqlSession, args);
// 關(guān)閉當前SqlSession
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);

關(guān)閉··SqlSesion··會判斷當前這個對象是否被Spring管理,沒有直接關(guān)閉,有則引用減一,交給Spring關(guān)閉

public final class SqlSessionUtils {
    
    public static void closeSqlSession(SqlSession session, 
            SqlSessionFactory sessionFactory) {
        //...

        SqlSessionHolder holder = (SqlSessionHolder) 
                TransactionSynchronizationManager.getResource(sessionFactory);
        if ((holder != null) && (holder.getSqlSession() == session)) {
            // ...
            // SqlSession引用減一
            holder.released();
        } else {
            // ...
            session.close();  // 直接關(guān)閉
        }
    }

    // 一個事務(wù)結(jié)束前會判斷當前SqlSession的引用是否為0
    // 為0則表示可以回收該SqlSession
    public void beforeCompletion() {
        // Issue #18 Close SqlSession and deregister it now
        // because afterCompletion may be called from a different thread
        if (!this.holder.isOpen()) {
            // ...
            // 從ThreadLocal中移除
            TransactionSynchronizationManager.unbindResource(sessionFactory);
            this.holderActive = false;
            // ...
            this.holder.getSqlSession().close();
        }
    }
}

SqlSession的引用變?yōu)?,那么就會從ThreadLocal中移除

public abstract class TransactionSynchronizationManager {

    // mybatis使用它保存當前線程信息 
    // Key為SqlSessionFactory value為SqlSessionHolder
    private static final ThreadLocal<Map<Object, Object>> resources =
            new NamedThreadLocal<>("Transactional resources");

    public static Object unbindResource(Object key) 
            throws IllegalStateException {
        Object actualKey = TransactionSynchronizationUtils
                                    .unwrapResourceIfNecessary(key);
        Object value = doUnbindResource(actualKey);
        // ...
        return value;
    }

    public static Object unbindResourceIfPossible(Object key) {
        Object actualKey = TransactionSynchronizationUtils
                                    .unwrapResourceIfNecessary(key);
        return doUnbindResource(actualKey);
    }

    private static Object doUnbindResource(Object actualKey) {
        Map<Object, Object> map = resources.get();
        if (map == null) {
            return null;
        }
        Object value = map.remove(actualKey);
        // Remove entire ThreadLocal if empty...
        if (map.isEmpty()) {
            resources.remove();
        }
    // ...
        return value;
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容