Mybatis-Spring:SqlSessionTemplate

环境: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源码,根据注释内容,它是由Spring管理的线程安全类,与Spring的事务管理器协作确保真实的SqlSession被关联到当前的Spring事务。更进一步,它基于Spring事务配置管理session的生命周期,包括关闭、提交、回滚

同时,因为是线程安全的,所以可以作为单例对象被所有的Dao使用

public class SqlSessionTemplate implements SqlSession, DisposableBean {

    private final SqlSessionFactory sqlSessionFactory;
    private final ExecutorType executorType;
    // SqlSession代理,通过它执行数据库操作
    // 通过这个代理对象,可以获取到专属于当前线程的SqlSession对象
    private final SqlSession sqlSessionProxy;  

    // 构造器
    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, 
            ExecutorType executorType, 
            PersistenceExceptionTranslator exceptionTranslator) {

        // ...

        this.sqlSessionFactory = sqlSessionFactory;
        this.executorType = executorType;
        this.exceptionTranslator = exceptionTranslator;
        // 使用JDK动态代理创建一个SqlSession代理对象
        this.sqlSessionProxy = (SqlSession) newProxyInstance(
                                                SqlSessionFactory.class.getClassLoader(),
                                                new Class[] { SqlSession.class },
                                                new SqlSessionInterceptor());
    }

    // 原生mybatis采用DefaultSqlSession发起数据库操作
    // mybatis-spring则通过一个代理对象,它代理的是原生DefaultSqlSession
    // 之所以使用代理,是为了获取当前线程专属的DefaultSqlSession
    public <T> T selectOne(String statement, Object parameter) {
        return this.sqlSessionProxy.selectOne(statement, parameter);
    }
}

根据上述代码段,可以知道SqlSessionTemplate也实现了SqlSession接口,同时组合了另一个SqlSession接口代理对象sqlSessionProxy执行具体的数据库操作,代理对象的增强逻辑如下

 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 {
            // 通过反射调用目标对象
            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) {
                // 关闭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) {
                // 关闭SqlSession
                closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
            }
        }
    }
  }

SqlSessionInterceptor中可以看到,代理对象还是先获取了一个SqlSession对象

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

SqlSessionTemplate作为一个全局单例的Bean,必然在多个Dao层中共同使用,那么不可避免会遇到Servelt容器的多线程环境所导致的线程安全问题,对此,它通过SqlSessionUtils解决了线程安全的问题

public final class SqlSessionUtils {

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

        // ...

        // SqlSessionTemplate的线程安全性由此保证
        // TransactionSynchronizationManager保证了线程安全
        // 追根揭底,这个类采用的技术还是ThreadLocal线程隔离技术
        SqlSessionHolder holder = (SqlSessionHolder) 
                TransactionSynchronizationManager.getResource(sessionFactory);

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

        // ...
        // 如果当前线程没有session,则通过SessionFactory创建
        // 这里就又回到了mybatis的DefaultSessionFactory,
        // 创建Mybatis原生的DefaultSqlSession
        session = sessionFactory.openSession(executorType);
        // 注册上面创建的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管理每个线程的资源和事务的同步状态,它采用了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应用这个方法将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);
        }
        // ...
    }
}

继续回到SqlSessionInterceptor,获取到当前线程专属的SqlSession之后,通过它处理完毕之后会关闭当前SqlSession,这也是Mybatis集成Spring之后一级缓存失效的原因

// 通过反射执行DefaultSqlSession对象的同名方法
Object result = method.invoke(sqlSession, args);
// 关闭当前SqlSession
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);

关闭··SqlSesion··会判断当前这个对象是否被Spring管理,没有直接关闭,有则引用减一,交给Spring关闭

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();  // 直接关闭
        }
    }

    // 一个事务结束前会判断当前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的引用变为0,那么就会从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;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容