1、產生的背景
使用MyBatis
就要保證SqlSession
實例的線程安全,就必須要爲每一次的請求單獨創建一個SqlSession
。但如果每一次請求都要用openSession()
自己去創建,就比較麻煩了。
所以在spring
中,我們使用的mybatis-spring
包中提供了一個線程安全的SqlSession
的包裝類sqlSessionTemplate
,用它來替代SqlSession
.因爲他是線程安全的,所以可以在所有的Service層來共享一個實例(默認爲單例)。
這個跟Spring
封裝其他的組件是一樣的,比如JdbcTemplate, RedisTemplate
等等,也是 Spring
跟MyBatis
整合的最關鍵的一個類。
2、創建SqlSessionTemplate
2.1 生成代理對象
SqlSessionTemplate
裏面有DefaultSqlSession
的所有的方法:select One()、select List()、insert()、update()、delete(),不過它都是通過一個代理對象實現的。這個代理對象在構造方法裏面通過一個代理類創建:
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
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class }, new SqlSessionInterceptor());
}
所有的方法都會先走到內部代理類 SqlSessionInterceptor
的 invoke()
方法:
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//獲取新的sqlSession,此處的爲線程安全的sqlSession(使用了threadLocal)
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
。。。
return result;
} catch (Throwable t) {
。。。。。。。。。。。。。。。。
}
}
在使用SqlSessionTemplate
調用方法的時候,先調用代理對象SqlSessionInterceptor
的invoke()
方法,通過getSqlSession()
獲取一個新的sqlSession對象,保證了每個請求是生成一個sqlSession,確保了線程安全。
2.2、獲取SqlSession
首先會使用工廠類、執行器類型、異常解析器創建一個 sqlSession
,然後再調用
sqlSession
的實現類,實際上就是在這裏調用了 DefaultSqlSession
的方法。此處調用的getSqlSession()
方法用來獲取一個SqlSession
對象。
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
//獲取SqlSessionHolder
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
LOGGER.debug(() -> "Creating a new SqlSession");
session = sessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
TransactionSynchronizationManager
獲取當前線程threadLocal
是否有SqlSessionHolder
,如果有就從SqlSessionHolder
取出當前SqlSession
,如果當前線程threadLocal
沒有SqlSessionHolder
,就從sessionFactory
中創建一個SqlSession
,最後註冊會話到當前線程threadLocal
中。
2.3 事務管理器
public abstract class TransactionSynchronizationManager {
private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);
// 存儲當前線程事務資源,比如Connection、session等
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
// 存儲當前線程事務同步回調器
// 當有事務,該字段會被初始化,即激活當前線程事務管理器
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
new NamedThreadLocal<>("Transaction synchronizations");
/**
*從當前線程的threadLocal中獲取SqlSessionHolder
*/
@Nullable
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;
}
@Nullable
private static Object doGetResource(Object actualKey) {
//private static final ThreadLocal<Map<Object, Object>> resources =
// new NamedThreadLocal<>("Transactional resources");
Map<Object, Object> map = resources.get();
if (map == null) {
return null;
}
Object value = map.get(actualKey);
// Transparently remove ResourceHolder that was marked as void...
if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
map.remove(actualKey);
// Remove entire ThreadLocal if empty...
if (map.isEmpty()) {
resources.remove();
}
value = null;
}
return value;
}
}
這是spring
的一個當前線程事務管理器,它允許將當前資源存儲到當前線程ThreadLocal
中,SqlSessionHolder
是保存在resources
中。
2.4 註冊SessionHolder
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
SqlSessionHolder holder;
// 判斷當前是否有事務
if (TransactionSynchronizationManager.isSynchronizationActive()) {
Environment environment = sessionFactory.getConfiguration().getEnvironment();
// 判斷當前環境配置的事務管理工廠是否是SpringManagedTransactionFactory(默認)
if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registering transaction synchronization for SqlSession [" + session + "]");
}
holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
// 綁定當前SqlSessionHolder到線程ThreadLocal中
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
// 註冊SqlSession同步回調器
TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
holder.setSynchronizedWithTransaction(true);
// 會話使用次數+1
holder.requested();
} else {
if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional");
}
} else {
throw new TransientDataAccessResourceException(
"SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
}
}
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because synchronization is not active");
}
}
}
註冊SqlSession
到當前線程事務管理器的條件首先是當前環境中有事務,否則不註冊,判斷是否有事務的條件是synchronizations
的ThreadLocal
是否爲空:
public static boolean isSynchronizationActive() {
return (synchronizations.get() != null);
}
每當我們開啓一個事務,會調用initSynchronization()
方法進行初始化synchronizations
,以激活當前線程事務管理器。
public static void initSynchronization() throws IllegalStateException {
if (isSynchronizationActive()) {
throw new IllegalStateException("Cannot activate transaction synchronization - already active");
}
logger.trace("Initializing transaction synchronization");
synchronizations.set(new LinkedHashSet<TransactionSynchronization>());
}
所以當前有事務時,會註冊SqlSession
到當前線程ThreadLocal
中。
2.5 小結
在mybatis
中SqlSession
是非線程安全的,如下:
/**
* The default implementation for {@link SqlSession}.
* Note that this class is not Thread-Safe.
*
* @author Clinton Begin
*/
public class DefaultSqlSession implements SqlSession {
}
在spring中,通過ThreadLocal
爲每一個請求創建一個SqlSession,這樣保證了線程的安全,從前面的背景中瞭解到在spring中是使用了SqlSession
的包裝類SqlSessionTemplate
,用它來替代SqlSession
。接下里就重點介紹SqlSessionTemplate
。
3、獲取SqlSessionTemplate
接着我們來講解怎麼獲取這個SqlSessionTemplate
,在 Service
層可以使用@Autowired
自動注入的 Mapper 接口,需要保存在
BeanFactory
(比如 XmlWebApplicationContext)中。也就是說接口肯定是在 Spring
啓動的時候被掃描了,註冊過的。我們先來看一下spring的配置文件applicationContext.xml
<bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.org.nci.henry.dao"/>
</bean>
3.1 MapperScannerConfigurer
MapperScannerConfigurer
實現了 BeanDefinitionRegistryPostProcessor
接口,
BeanDefinitionRegistryPostProcessor
是BeanFactoryPostProcessor
的子類,可以通過編碼的方式修改、新增或者刪除某些 Bean 的定義。類圖如下:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Y1G6wflY-1590221543779)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200523123506426.png)]
spring初始化ioc容器的時候,會調用postProcessBeanDefinitionRegistry()
方法;
//MapperScannerConfigurer.java
MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
scanner.scan()
方 法 是 ClassPathBeanDefinitionScanner
中 的 , 而 它 的 子 類
ClassPathMapperScanner
覆 蓋 了 doScan()
方 法 , 在 doScan()
中 調 用 了
processBeanDefinitions()
。
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
調用子類doScan()
方法,把接口全部注添加到beanDefinitions
中。
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
.............
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
+ "' package. Please check your configuration.");
} else {
//對生成的beanDefinition做處理
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
}
3.2 MapperFactoryBean
調用processBeanDefinitions()
,在註冊前將beanClass
設置爲MapperFactoryBean
,也就是說所有的Mapper接口在容器裏都被註冊成了一個MapperFactoryBean
.
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
................
/**核心在此處,爲definition設置beanClass,也就是對bd做後置處理,改變bd *的屬性,設置成mapperFactoryBeanClass,此爲MapperFactoryBean對象
*/
definition.setBeanClass(this.mapperFactoryBeanClass);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
}
}
來看下MapperFactoryBean
這個類
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
}
這個類繼承了SqlSessionDaoSupport
,我們知道這個類中包含有spring操作MyBatis的sqlSessionTemplate
的對象。
public abstract class SqlSessionDaoSupport extends DaoSupport {
private SqlSessionTemplate sqlSessionTemplate;
}
因爲MapperFactoryBean
實現了FactoryBean
接口,所以在實例化的時候會調用getObject()
方法,瞭解過MyBatis源碼的同學應該就能知道,此處調用getMapper()
是爲了生成一個代理對象。這個我會在後續的文章中講解MyBatis源碼的時候將調用getMapper()
方法生成代理對象的流程做詳細介紹,此處不再贅述。
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
3.3 SqlSessionTemplate
先通過調用父類SqlSessionDaoSupport
的getSqlSession()
拿到sqlSessionTemplate
對象,
public SqlSession getSqlSession() {
return this.sqlSessionTemplate;
}
繼續調用SqlSessionTemplate
的getMapper()
public class SqlSessionTemplate implements SqlSession, DisposableBean {
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
@Override
public Configuration getConfiguration() {
//獲取Configuration對象
return this.sqlSessionFactory.getConfiguration();
}
}
3.4 Configuration
調用Configuration
類中getMapper
public class Configuration {
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
}
public class MapperRegistry {
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//生成動態代理
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
}
後面就是MyBatis
中生成mapper代理對象的流程了,後續會在MyBatis源碼篇中詳細講解,此處不再詳細贅述。
通 過 工 廠 類MapperProxyFactory
獲得一個MapperProxy
代理對象。也就是說,我們注入到Service
層的接口,實際上還是一個 MapperProxy
代理對象。所以最後調用 Mapper
接口的方法,也是執行 MapperProxy
的 invoke()
方法.
3.5 小結
簡單總結一下上面的流程:
1)MapperScannerConfigurer
實現了BeanDefinitionRegistryPostProcessor
類,spring初始化IoC容器bean名字爲mapperScannerConfigurer
的時候,會調用postProcessBeanDefinitionRegistry()
方法;
2)通過MapperScannerConfigurer
調用postProcessBeanDefinitionRegistry
,來進行指定mapper包的掃描,從而獲取到beanDefinition
;
3)對beanDefinition
進行處理,通過ClassPathMapperScanner
類中的processBeanDefinitions()
的對bean進行重新處理:
//這個definition就變成了mapperFactoryBeanClass對象(MapperFactoryBean) definition.setBeanClass(this.mapperFactoryBeanClass);
MapperFactoryBean
是一個繼承了SqlSessionDaoSupport
的類,它可以對sqlSessionTemplate
進行一系列的操作,SqlSessionTemplate
實現了SqlSession
的接口,爲了方便理解,我們可以簡單的把SqlSessionTemplate
當成是MyBatis
中的sqlSession
來使用,而spring爲了更加便捷的使用MyBatis,所以將sqlSession,封裝成了SqlSessionTemplate
來使用。
4)在初始化掃描到的mapper對應的bean時,獲取到的是MapperFactoryBean
的bean,此時需要調用該類的getObject()
方法,才能獲取真正的bean.
5)先調用getSqlSession()
方法獲取到sqlSessionTemplate
,調用getMapper()
方法生成mapper相關的代理對象,這樣我們就可以直接在業務代碼中加入@Autowired
進行依賴注入,然後輕鬆使用就可以了。
@Autowired
private UserMapper userMapper;
4、應用:
經過上面的分析我們基本瞭解獲取sqlSessionTemplate
的基本原理,所以我們使用 Mapper
的時候,只需要在加了 Service
註解的類裏面使用@Autowired
注入 Mapper
接口就好了。
@Service
public class User UserService {
@Autowired
private UserMapper userMapper;
public List<User> getAll() {
return userMapper.selectByAll();
}
}
通過每一個注入Mapper
的地方,都可以拿到sqlSessionTemplate
。