环境: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;
}
}