一、前言
SpringBoot
结合MyBatisPlus
是如何注入SqlSession
的?SqlSession
如何实现在相同请求是相同对象,不同请求中是不同对象?
二、注入的 SqlSession
@Autowired
private SqlSession sqlSession;
@GetMapping("/test")
public void test(){
log.info(sqlSession.toString());
log.info(sqlSession.toString());
}
通过上面的代码可以发现:每次请求中打印的 sqlSession
都是同一个对象,但是为什么又说 sqlSession
的生命周期是一个会话(请求)?
1、其实通过断点可以发现注入的 sqlSession
的类型是 SqlSessionTemplate
,而不是我们熟知的 DefaultSqlSession
;sqlSession
自动注入源码如下:
@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisPlusProperties.class)
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisPlusLanguageDriverAutoConfiguration.class})
public class MybatisPlusAutoConfiguration implements InitializingBean {
// ...
/**
* 当容器中不存在 SqlSessionTemplate 类型的 Bean 的时候
* 创建一个 SqlSessionTemplate 对象并注入
*/
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
if (executorType != null) {
return new SqlSessionTemplate(sqlSessionFactory, executorType);
} else {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
}
关于 SqlSessionTemplate
官方解释如下:
线程安全的,Spring管理的,与Spring事务管理一起工作的SqlSession,以确保实际使用的SqlSession 与当前Spring事务相关联。此外,它还管理会话生命周期,包括根据Spring事务配置根据需要关闭、提交或回滚会话。
模板需要一个SqlSessionFactory来创建SqlSessions,作为构造函数参数传递。它也可以被构造为指示要使用的执行程序类型,如果不是,则将使用会话工厂中定义的默认执行程序类型。
2、通过查看 SqlSessionTemplate
的成员属性可以发现下面这个字段 sqlSessionProxy
/**
* SqlSessionTemplate 是典型的装饰者模式:
* 在不改变现有对象结构的情况下,动态地给该对象增加一些其额外功能的模式。
*/
public class SqlSessionTemplate implements SqlSession, DisposableBean {
//实际工作的 sqlSession 代理类,类型是 DefaultSqlSession
private final SqlSession sqlSessionProxy;
@Override
public int insert(String statement) {
return this.sqlSessionProxy.insert(statement);
}
}
通过断点多次请求可以发现,注入的 SqlSession
(类型是 SqlSessionTemplate
)是同一个对象,其中的 sqlSessionProxy
属性也是同一个的对象。
从上面的 insert
方法中可以看出实际走的是 sqlSessionProxy
的代理方法
sqlSessionProxy
属性的赋值是在 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;
// jdk 动态代理
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor()
);
}
此时还不能解决我们的疑问:多次请求注入的同一 sqlSessionTemplate
中 sqlSessionProxy
也是相同的,但每次请求实际调用 insert
的对象不应该是同一个?
此时就可以将目光转向 JDK 动态代理的调用处理器 SqlSessionInterceptor
中
SqlSessionInterceptor
是 sqlSessionTemplate
的子类
public class SqlSessionTemplate implements SqlSession, DisposableBean {
private class SqlSessionInterceptor implements InvocationHandler {
/**
* sqlSessionProxy 每次执行 SqlSession 接口的方法,都是执行下面的代理增强方法
* proxy即sqlSessionProxy,但此处没有用到
*/
@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) {
//...
} finally {
//...
}
}
}
}
在此其实不难猜出 getSqlSession()
方法的主要作用是获取当前线程(请求)所属的 sqlSession
,在这也可以确定 sqlSession
其实是放在 ThreadLocal
中
/**
* 从Spring事务管理器中获取一个SqlSession,或者根据需要创建一个新的。
* 尝试从当前事务中获取SqlSession。如果没有,则创建一个新的。
* 然后,如果Spring TX是开启的,并且SpringManagedTransactionFactory
* 被配置为事务管理器,它就会将SqlSession与事务同步。
*/
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory,
ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
// 从Spring事务管理器中获取当前线程的SqlSessionHolder
// SqlSessionHolder中成员属性存放了SqlSession
// 见下一段代码
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
// 其实就是一个get方法session = holder.getSqlSession();
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
// 如果当前线程没有 SqlSession ,则创建一个
LOGGER.debug(() -> "Creating a new SqlSession");
session = sessionFactory.openSession(executorType);
// 储存创建的 SqlSession
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
**获取SqlSessionHolder
**
从 TransactionSynchronizationManager
获取 SqlSessionHolder
/**
* TransactionSynchronizationManager 线程资源管理器
* 管理每个线程的资源和事务同步的中央委托。
*/
public abstract class TransactionSynchronizationManager {
// -------------- 资源存储 -----------------
// 事务资源
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
new NamedThreadLocal<>("Transaction synchronizations");
// 当前事务名
private static final ThreadLocal<String> currentTransactionName =
new NamedThreadLocal<>("Current transaction name");
// 当前事务是否只读
private static final ThreadLocal<Boolean> currentTransactionReadOnly =
new NamedThreadLocal<>("Current transaction read-only status");
// 当前事务的隔离级别
private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
new NamedThreadLocal<>("Current transaction isolation level");
// 当前事务的活动状态
private static final ThreadLocal<Boolean> actualTransactionActive =
new NamedThreadLocal<>("Actual transaction active");
// -------------- 获取资源方法 ---------------------
@Nullable
public static Object getResource(Object key) {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Object value = doGetResource(actualKey);
// 日志 ...
return value;
}
@Nullable
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;
}
}