前言
在 上一篇 文章中,我们知道 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);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx);
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) {
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;
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 = 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) {
}
throw unwrapped;
} finally {
if (sqlSession != null) {
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);
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);
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) {
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 映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<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 = 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 = new SqlSessionFactoryBuilder().build(reader);
SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory);
StudentMapper studentMapper = sqlSessionTemplate.getMapper(StudentMapper.class);
studentMapper.update(100, "hello");
}
}
上述通过 interface 来执行数据库操作的使用方式应该是最常用的,那么该方式背后又是如何实现的呢?请看下回分解 点这里
|