在普遍的JAVA-WEB项目的实际业务处理中,最终都是通过SqlSessionTemplate执行数据库的CURD操作。本文结合mybatis源码,对SqlSessionTemplate进行详细的介绍。
SqlSessionTemplate是个线称安全的类,如果你的系统是微服务架构的话,那一个微服务(组件)里面的所有DAO可以共享同一个SqlSessionTemplate对应的bean实例。因为SqlSessionTemplate实现了SqlSession接口,他会保证使用的SqlSession是和当前Spring的事务相关的,并且他还会管理session的生命周期,包含关闭、提交和回滚操作。
那他具体是如何实现的呢,下面进行源码解析。
首先,SqlSessionTemplate在进行初始化的时候,会把SqlSessionFactory作为参数传入:
此处,在初始化SqlSessionTemplate实例的时候,创建一个SqlSessionFactory的代理类作为SqlSessionTemplate的一个属性是关键点。
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
// 此处创建了sqlSession的代理类的实例,当通过代理类进行方法调用时
// 该调用会被导向SqlSessionInterceptor的invoke方法
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[] {
SqlSession.class }, new SqlSessionInterceptor());
}
创建代理的目的就是执行切面方法,下面进行切面功能的详细解读:
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 获取SqlSession(这个SqlSession才是真正使用的,它是线程独有的)
// 对于getSqlSession方法的具体实现,在下面有详细解读
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
// 调用从Spring的事物上下文中获取的sqlSession
// 结合具体的参数(args)和sqlSession,进行最终的sql执行,操作数据库
Object result = method.invoke(sqlSession, args);
// 然后判断一下当前的sqlSession是否被Spring托管 如果未被Spring托管则自动commit
if (!isSqlSessionTransactional(sqlSession,
SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
// 如果出现异常则根据情况转换后抛出
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null &&
unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the
// translator is no loaded. See issue #22
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的具体实现,是根据当前sqlSession是否在Spring的事物上下文中来决定具体操作的
// 如果sqlSession被Spring管理,则调用holder.released(),使计数器-1
// 否则直接关闭当前的sqlSession
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
getSqlSession方法如下:
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory,
ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
// 根据sqlSessionFactory,从TransactionSynchronizationManager定义的资源map中
// 获取当前线程对应的SqlSessionHolder
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.
getResource(sessionFactory);
// 从SqlSessionHolder中提取SqlSession对象
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Creating a new SqlSession");
}
// 如果无法通过SqlSessionHolder获取到sqlSession,那就利用sessionFactory新创建一个sqlSession
session = sessionFactory.openSession(executorType);
// 新创建一个SqlSessionHolder,并且将刚创建的sqlSession与其绑定
// 然后将SqlSessionHolder对象注册到TransactionSynchronizationManager中
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
综上所述,通过对SqlSessionTemplate的源码解读,我们应该可以理解为什么可以在一个微服务(组件)里面的所有DAO可以共享同一个SqlSessionTemplate对应的bean实例,并保证线程安全和事务的完整性,因为:
1、多个线程都通过调用同一个SqlSessionTemplate进行数据库操作时,SqlSessionTemplate通过代理机制,每一次都执行切面里的getSqlSession方法来获取真正操作数据库的sqlSession。
2、在getSqlSession方法中,使用到了重要的TransactionSynchronizationManager类,此类利用ThreadLocal方式,将当前线程与一个事物相绑定,从而在当前线程想要获取sqlSession时,判断当前线程是否存在还未完成的事物,如果存在则利用与当前事物绑定的sqlSession执行数据库操作,如果不存在则新建sqlSession进行相应操作,从而保证了线程的安全和数据库事物的完整性。