mybatis与spring的整合之SqlSessionTemplate
使用 SqlSession
- 在
MyBatis
中,你可以使用 SqlSessionFactory 来创建 SqlSession。一旦你获得一个session
之后,你可以使用它来执行映射语句,提交或回滚连接。最后,当不再需要它的时候, 你可以关闭session
。 - 使用
MyBatis-Spring
之后, 你不再需要直接使用 SqlSessionFactory 了,因为你的bean
可以通过一个线程安全的 SqlSession 来注入,基于Spring
的事务配置来自动提交、回滚、关闭session
。
SqlSessionTemplate
SqlSessionTemplate 是 MyBatis-Spring
的核心。 这个类负责管理 MyBatis
的 SqlSession, 调用 MyBatis
的 SQL
方法, 翻译异常。 SqlSessionTemplate 是线程安全的, 可以被多个 DAO
所共享使用。
当调用 SQL
方法时, 包含从映射器 getMapper()
方法返回的方法, SqlSessionTemplate 将会保证使用的 SqlSession 是和当前 Spring
的事务相关的。此外,它管理 session
的生命 周期,包含必要的关闭,提交或回滚操作。
SqlSessionTemplate 实现了 SqlSession 接口,这就是说,在代码中无需对 MyBatis
的 SqlSession 进行替换。 SqlSessionTemplate 通常是被用来替代默认的 MyBatis
实现的 DefaultSqlSession , 因为模板可以参与到 Spring
的事务中并且被多个注入的映射器类所使 用时也是线程安全的。相同应用程序中两个类之间的转换可能会引起数据一致性的问题。
SqlSessionTemplate 对象可以使用 SqlSessionFactory 作为构造方法
的参数来创建。
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
源码
SqlSessionTemplate只有构造方法注入
,没有setter
方法
SqlSessionTemplate构造方法
一个入参
这是用得最多
的构造方法,注入一个SqlSessionFactory
sqlSessionFactory.getConfiguration().getDefaultExecutorType()
,获取Configuration默认的executorType
,为SIMPLE
。Configuration的初始化可详见mybatis与spring的整合之SqlSessionFactoryBean
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}
两个入参
一般执行批量操作时需要改变executorType
为BATCH
MyBatisExceptionTranslator是异常转换器
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
}
三个入参
一般都是SqlSessionTemplate内部自己调用,完成属性赋值
其中最重要的是sqlSessionProxy
,通过JDK
动态 代理创建的session
,执行sql
语句是通过这个代理对象完成的
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
Assert.notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
}
动态代理创建sqlSession
JDK
动态代理需要有一个实现InvocationHandler接口的实现类
SqlSessionTemplate的内部类
SqlSessionInterceptor
private class SqlSessionInterceptor implements InvocationHandler {
private SqlSessionInterceptor() {
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取SqlSession(这个SqlSession才是真正使用的,它不是线程安全的)
//这个方法可以根据Spring的事物上下文来获取事物范围内的sqlSession
SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
Object unwrapped;
try {
// 调用真实SqlSession的方法
Object result = method.invoke(sqlSession, args);
// 然后判断一下当前的sqlSession是否被Spring托管 如果未被Spring托管则自动commit
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) {
// 异常关闭session
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) {
// 关闭sqlSession
// 它会根据当前的sqlSession是否在Spring的事物上下文当中来执行具体的关闭动作
// 如果sqlSession被Spring管理 则调用holder.released(); 使计数器-1
// 否则才真正的关闭sqlSession
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
return unwrapped;
}
}
SqlSessionUtils.getSqlSession创建sqlSession
- 根据
sqlSessionFactory
从当前线程
对应的资源map中获取SqlSessionHolder - 如果找不到,则根据执行类型构造一个新的
sqlSession
- 将新构造的
sqlSession
注册进事务管理器中
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
Assert.notNull(sessionFactory, "No SqlSessionFactory specified");
Assert.notNull(executorType, "No ExecutorType specified");
// 根据sqlSessionFactory从当前线程对应的资源map中获取SqlSessionHolder
// 当sqlSessionFactory创建了sqlSession,就会在事务管理器中添加一对映射
// key为sqlSessionFactory,value为SqlSessionHolder,该类保存sqlSession及执行方式
SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
// 获取session
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
// session若不为空直接返回
return session;
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Creating a new SqlSession");
}
// 如果找不到,则根据执行类型构造一个新的sqlSession
session = sessionFactory.openSession(executorType);
// 将sqlSession注册进事务管理器中
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
}
从事务管理器中寻找sqlSession
- 如果
holder
不为空,且和当前事务同步 - 从
holder
中取出sqlSession
- 增加引用数
private static SqlSession sessionHolder(ExecutorType executorType, SqlSessionHolder holder) {
SqlSession session = null;
// 如果holder不为空,且和当前事务同步
if (holder != null && holder.isSynchronizedWithTransaction()) {
// hodler保存的执行类型和获取SqlSession的执行类型不一致,就会抛出异常
// 也就是说在同一个事务中,执行类型不能变化,原因就是同一个事务中同一个sqlSessionFactory创建的sqlSession会被重用
if (holder.getExecutorType() != executorType) {
throw new TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction");
}
// 增加该holder,也就是同一事务中同一个sqlSessionFactory创建的唯一sqlSession,其引用数增加,被使用的次数增加
holder.requested();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Fetched SqlSession [" + holder.getSqlSession() + "] from current transaction");
}
// 返回该session
session = holder.getSqlSession();
}
return session;
}
根据执行类型构造一个新的sqlSession
SessionFactory.openSession(executorType)
public SqlSession openSession(ExecutorType execType) {
return this.openSessionFromDataSource(execType, (TransactionIsolationLevel)null, false);
}
第三个参数autoCommit
默认为false
,关闭自动提交
- 从
configuration
配置环境中获取事物工厂
,默认为SpringManagedTransactionFactory,由Spring
管理事务 - 根据
数据源
与自动提交类型
构建事物
- 根据
事物
与执行器类型
构建执行器
- 最后构建SqlSession,就是DefaultSqlSession
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
try {
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
// 创建事物
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 构造执行器,底层是通过执行器完成sql语句
Executor executor = this.configuration.newExecutor(tx, execType);
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
}
return var8;
}
将sqlSession注册进事务管理器中
- 判断同步是否激活,只要
SpringTX
被激活,就是true
,比如@EnableTransactionManagement
- 判断配置的事物是否由
spring管理
- 将当前
sqlSession
加载进事务管理的本地线程缓存中,构造成SqlSessionHolder - 将
holder
,sessionFactory
的同步加入本地线程缓存中 - 设置当前
holder
和当前事务同步 - 增加引用数
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
// 判断同步是否激活,只要SpringTX被激活,就是true
if (TransactionSynchronizationManager.isSynchronizationActive()) {
// 加载环境变量,判断注册的事务管理器是否是SpringManagedTransaction,也就是Spring管理事务
Environment environment = sessionFactory.getConfiguration().getEnvironment();
if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registering transaction synchronization for SqlSession [" + session + "]");
}
// 如果是,则将sqlSession加载进事务管理的本地线程缓存中
SqlSessionHolder holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
// 以sessionFactory为key,hodler为value,加入到TransactionSynchronizationManager管理的本地缓存ThreadLocal<Map<Object, Object>> resources中
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
// 将holder, sessionFactory的同步加入本地线程缓存中ThreadLocal<Set<TransactionSynchronization>> synchronizations
TransactionSynchronizationManager.registerSynchronization(new SqlSessionUtils.SqlSessionSynchronization(holder, sessionFactory));
// 设置当前holder和当前事务同步
holder.setSynchronizedWithTransaction(true);
// 增加引用数
holder.requested();
} else {
if (TransactionSynchronizationManager.getResource(environment.getDataSource()) != null) {
throw new TransientDataAccessResourceException("SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional");
}
}
} else if (LOGGER.isDebugEnabled()) {
LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because synchronization is not active");
}
}
method.invoke反射执行sql语句
这里找到一篇文章解释比较好的Mybatis的SqlSession运行原理
SqlSessionUtils.closeSqlSession关闭sqlSession
判断sqlSession
是否被Spring
事务管理
- 是: 交由spring关闭
- 不是: 自己关闭
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
Assert.notNull(session, "No SqlSession specified");
Assert.notNull(sessionFactory, "No SqlSessionFactory specified");
// 其实下面就是判断session是否被Spring事务管理,如果管理就会得到holder
SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
if (holder != null && holder.getSqlSession() == session) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Releasing transactional SqlSession [" + session + "]");
}
// 这里释放的作用,不是关闭,只是减少一下引用数,因为后面可能会被复用
holder.released();
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Closing non transactional SqlSession [" + session + "]");
}
// 如果不是被spring管理,那么就不会被Spring去关闭回收,就需要自己close
session.close();
}
}