在 上一篇 文章中,我们知道 MyBatis
有四大核心对象,分别是 SqlSessionFactoryBuilder,SqlSessionFactory,SqlSession,Mapper
映射器,而 MyBatis
在和 Spring
整合之后,Spring
会帮助我们管理 SqlSessionFactory、SqlSession、Mapper
映射器这些核心 bean
;在 这一篇 文章中已经阐述了 SqlSessionFactory
对象的产生过程,在本篇文章中就是来阐述 SqlSession
这个对象是如何产生的
SqlSession
对象的产生在 MyBatis
中我们获取 SqlSession
的实例,实际上使用的都是 new DefaultSqlSession()
的方式,源码在 DefaultSqlSessionFactory
类中如下
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
// MyBatis源码:使用的是 new DefaultSqlSession()的方式,返回一个 SqlSession 对象
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
而 MyBatis
在和 Spring
整合之后,我们是不能使用 new DefaultSqlSession()
的方式来创建 SqlSession
的,因为 DefaultSqlSession
类在 MyBatis
中是线程不安全的
Spring
为了解决类 DefaultSqlSession
线程不安全的问题,为我们提供了一个线程安全的 SqlSessionTemplate
类
SqlSessionTemplate
类登场SqlSessionTemplate
类简介该类同我们在 MyBatais
中使用的 DefaultSqlSession
类相同,也都为我们提供了直接操作数据库的一些方法,比如:selectOne(),selectList(),insert(),update(),delete()
等
SqlSessionTemplate
是 mybatis-spring
的核心。这个类负责管理 MyBatis
的 SqlSession
,调用 MyBatis
的 SQL
方法。SqlSessionTemplate
是线程安全的,可以被多个 DAO
所共享使用SqlSessionTemplate
实现了 SqlSession
接口,SqlSessionTemplate
通常是被用来替代默认的 MyBatis
实现的 DefaultSqlSession
, 因为模板可以参与到 Spring
的事务中并且被多个注入的映射器类所使用时也是线程安全的sqlSessionProxy
初始化我们从 SqlSessionTemplate
类的 selectOne()
方法入手来分析,发现此处使用的是 SqlSession
类型的 sqlSessionProxy
的一个对象,这个对象我们通过名称可以看出它是一个代理类
public class SqlSessionTemplate implements SqlSession, DisposableBean {
private final SqlSessionFactory sqlSessionFactory;
private final ExecutorType executorType;
private final SqlSession sqlSessionProxy;
@Override
public <T> T selectOne(String statement) {
// 使用 sqlSessionProxy 来调用 selectOne() 方法
return this.sqlSessionProxy.<T> selectOne(statement);
}
// 部分代码省略......
}
那么 sqlSessionProxy
在哪里初始化呢?接着看源码如下
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;
// 初始化 sqlSessionProxy 代理对象,基于JDK动态代理创建代理对象
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
在 SqlSessionTemplate
类中共有 3
个构造方法,其最终内部都是调用上述的构造方法来完成初始化的。这个方法的关键是内部通过调用 JDK
动态代理 Proxy.newProxyInstance()
方法创建了一个 SqlSession
代理对象并赋值给了 sqlSessionProxy
;此时,我们需要把目光聚焦到 SqlSessionInterceptor
类的实现逻辑上,因为其中包含该代理对象的代理逻辑,其代码逻辑如下
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 {
// 然后再调用sqlSession的实现类,实际上就是在这里调用了DefaultSqlSession的方法
Object result = method.invoke(sqlSession, args);
// 此处是通过 Transactional 事务的方式
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// sqlSession提交
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
// 省略......
}
throw unwrapped;
} finally {
if (sqlSession != null) {
// 关闭sqlSession
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
发现了没,在代理逻辑中先通过 getSqlSession()
方法获取 SqlSession
,而后执行 method.invoke()
方法,之后通过 isSqlSessionTransactional()
方法判断当前操作是否是事务操作,如果属于事务操作则执行 sqlSession.commit()
方法,以此完成了事务的提交
我们继续对 getSqlSession()
方法进行分析,代码如下
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
// 此处,通过事务管理器的 getResource()方法,将我们的sqlSessionFactory工厂类传入
// 返回的是一个 SqlSessionHolder持久器类
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Creating a new SqlSession");
}
session = sessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
我们继续对 getResource()
方法进行分析,代码如下
public abstract class TransactionSynchronizationManager {
private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources");
public static Object getResource(Object key) {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
// 执行 doGetResource()方法
Object value = doGetResource(actualKey);
if (value != null && logger.isTraceEnabled()) {
logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
}
return value;
}
private static Object doGetResource(Object actualKey) {
// 我们通过 resources.get() 的方式来获得,我们对这个 resources 属性进行分析
// 发现它返回的 SqlSession的持久器 SqlSessionHolder 也是一个ThreadLocal对象
// 即:我们每一个获取到的 SqlSession都是有自己的SqlSessionHolder对象的
// 此处,一个事务一个SqlSession。本身ThreadLocal 就是线程安全的,所以每一个SqlSession 也都是线程安全的
Map<Object, Object> map = (Map)resources.get();
if (map == null) {
return null;
} else {
Object value = map.get(actualKey);
if (value instanceof ResourceHolder && ((ResourceHolder)value).isVoid()) {
map.remove(actualKey);
if (map.isEmpty()) {
resources.remove();
}
value = null;
}
return value;
}
}
}
由此通过 SqlSessionTemplate
就可以创建线程安全的 SqlSession
对象了
在 SqlSessionTemplate
类中有一个SqlSession
类型的 sqlSessionProxy
变量,他其实是 SqlSession
的代理类 SqlSessionInterceptor
SqlSessionTemplate
执行方法(增删改查)的时候,最后都给交给 SqlSessionInterceptor
来代理执行SqlSessionInterceptor
每次在获取 SqlSession
的时候,都会使用事务管理器从 Threadlocal
中获取,所以必然是线程安全的!对于 Spring
而言,每个事务都会使用同一个 SqlSession
,其实也就是 DefaultSqlSession
,用于操作相应的 executor
来进行 db
交互SqlSessionTemplate
的使用Mapper
映射文件
<mapper namespace="com.jorg.mybatis.mappers.StudentMapper">
<update id="update" parameterType="java.util.Map">
UPDATE
student
SET
`name` = #{name}
WHERE
id = #{id}
update>
mapper>
public class SqlSessionTemplateTest {
public static void main(String[] args) {
Reader reader = Resources.getResourceAsReader("resource/mybatis-config.xml");
// 获取 SqlSessionFactory 对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory);
HashMap<String, Object> paramMaps = Maps.newHashMap();
paramMaps.put("name", "hello");
paramMaps.put("id", "100");
sqlSessionTemplate.update("update", paramMaps);
}
}
SqlSessionTemplate
时,是无需通过调用 commit()
方法就可以完成真正的更新。原因就在于 SqlSessionInterceptor
类中会执行 method.invoke()
方法,之后通过 isSqlSessionTransactional()
方法判断当前操作是否是事务操作,如果属于事务操作则执行 sqlSession.commit()
方法,以此完成了事务的提交在日常开发中,应该很少以上述的方式来使用 MyBatis
,更多的则是将对应 Mapper
配置关联到相应的 interface
,并直接调用 interface
中定义的方法来操作数据库。如下所示
public class SqlSessionTemplateTest {
public static void main(String[] args) {
Reader reader = Resources.getResourceAsReader("resource/mybatis-config.xml");
// 获取 SqlSessionFactory 对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory);
StudentMapper studentMapper = sqlSessionTemplate.getMapper(StudentMapper.class);
studentMapper.update(100, "hello");
}
}
上述通过 interface
来执行数据库操作的使用方式应该是最常用的,那么该方式背后又是如何实现的呢?请看下回分解 点这里