【Mybatis原始碼解析】-mybatis-spring原理解析
mybatis-spring原理解析
沒有spring使用Mybatis的步驟是
1:建立SqlSessionFactoryBuilder
2: 通過SqlSessionFactoryBuilder建立要給SqlSessionFactory
3: 建立SqlSession
4: 獲取Mapper的代理物件
5:執行獲取結果
很多都是重複步驟,可以交給spring去管理
mybatis-spring
就是幫助處理這些工作,
Mybatis-spring主要做的內容包含:
- mybatis相關類 “Spring”化,都註冊到Spring 容器中,對mapper額外提供批量掃描功能。
- 事務對接Spring,SqlSession交由Spring事務管理。
我們從使用過程到執行過程分步講解原理。
1、初始化過程
入口:SqlSessionFactoryBean
使用Mybatis-spring,需要主動配置SqlSessionFactoryBean,所以配置初始化的流程從這裡開始。
SqlSessionFactoryBean實現了FactoryBean
,InitializingBean
,ApplicationListener
-
FactoryBean,用於自定義Bean例項化邏輯,並註冊到Spring容器。SqlSessionFactory是一個很重的類,例項的化的過程比較複雜繁瑣。
-
ApplicationListener,監聽的是ContextRefreshedEvent事件,配置了快速失敗時檢查MapperedStatement是否載入完畢。
-
InitializingBean,真正開始例項化的時機,開始構建SqlSessionFactory。
1.1、SqlSessionFactory初始化
@Override public void afterPropertiesSet() throws Exception { notNull(dataSource, "Property 'dataSource' is required"); notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required"); state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null), "Property 'configuration' and 'configLocation' can not specified with together"); // 開始構造 this.sqlSessionFactory = buildSqlSessionFactory(); }
構造過程:也就是解析配置檔案,構造Configuration過程
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
final Configuration targetConfiguration;
XMLConfigBuilder xmlConfigBuilder = null;
// 指定了mybatis-config.xml的路徑時
else if (this.configLocation != null) {
// 構造xml解析器
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
targetConfiguration = xmlConfigBuilder.getConfiguration();
}
// 啥都沒指定,直接例項化Configuration,使用預設配置
else {
LOGGER.debug(
() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
targetConfiguration = new Configuration();
Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
}
// 預設配置設定
Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
if (hasLength(this.typeAliasesPackage)) {
scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
.filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
.filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
}
// 別名註冊
if (!isEmpty(this.typeAliases)) {
Stream.of(this.typeAliases).forEach(typeAlias -> {
targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
});
}
// 外掛註冊
if (!isEmpty(this.plugins)) {
Stream.of(this.plugins).forEach(plugin -> {
targetConfiguration.addInterceptor(plugin);
LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
});
}
// 自定義型別處理器註冊
if (hasLength(this.typeHandlersPackage)) {
scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
.filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
.forEach(targetConfiguration.getTypeHandlerRegistry()::register);
}
if (!isEmpty(this.typeHandlers)) {
Stream.of(this.typeHandlers).forEach(typeHandler -> {
targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
});
}
if (!isEmpty(this.scriptingLanguageDrivers)) {
Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
targetConfiguration.getLanguageRegistry().register(languageDriver);
LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");
});
}
Optional.ofNullable(this.defaultScriptingLanguageDriver)
.ifPresent(targetConfiguration::setDefaultScriptingLanguage);
if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls
try {
targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
}
Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);
// 執行了Config路徑時需要解析
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
targetConfiguration.setEnvironment(new Environment(this.environment,
this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
this.dataSource));
// mapper.xml檔案地址
if (this.mapperLocations != null) {
if (this.mapperLocations.length == 0) {
LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
} else {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
// 遍歷解析xml並註冊
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
}
// 將設定好的Configuration交由SqlSessionFactoryBuilder進行構造
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
SqlSessionFactory就已經初始化完畢了,SqlSessionFactory的例項單例儲存在Spring容器中,具體在Mybatis的哪個類中自動使用,稍後再說。
1.2 Mapper掃描、註冊
mapper掃描路徑配置常見有兩種方式
- MapperScan註解,標註在應用的根目錄下,自動掃描自包的Mapper檔案。
- 手動配置MapperScannerConfigurer,這個Bean,也是指定掃描路徑。
因為MapperScan註解最終也是生成MapperScannerConfigurer類的例項,其使用也更方便,那麼就從MapperScan開始介紹。
過程概覽:
- MapperScan註解通過@Import(MapperScannerRegistrar.class),匯入了MapperScannerRegistrar類
- MapperScannerRegistrar獲得MapperScan註解上的值(主要是basePackages),構造MapperScannerConfigurer的BeanDefinition並註冊。
- postProcessBeanDefinitionRegistry回撥進行掃描動作,例項化臨時的ClassPathMapperScanner,藉助其完成掃描註冊。
- ClassPathMapperScanner藉助Spring的ClassPathBeanDefinitionScanner掃描Mapper,替換每個Mapper BeanDefinition的資訊。完成Mapper的 mybatis和Spring的對接。
1.2.1 MapperScan
主要作用就是:主要作用就是提供可配置路徑,匯入MapperScannerRegistrar。比較簡單。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
//匯入Bean,
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan
1.2.2 MapperScannerRegistrar
MapperScannerRegistrar就是一個mapper掃描註冊器,用於讀取註解上的值並註冊一個Mapper掃描配置類。
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware
藉助Spring的import機制,構造並註冊MapperScannerConfigurerBeanDefinition
void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
// mapperScanner註解屬性構造,構造MapperScannerConfigurer的BeanDefinition
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
builder.addPropertyValue("annotationClass", annotationClass);
}
Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
builder.addPropertyValue("markerInterface", markerInterface);
}
Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
}
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
}
String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
if (StringUtils.hasText(sqlSessionTemplateRef)) {
builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
}
String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
if (StringUtils.hasText(sqlSessionFactoryRef)) {
builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
}
List<String> basePackages = new ArrayList<>();
basePackages.addAll(
Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
.collect(Collectors.toList()));
basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName)
.collect(Collectors.toList()));
String lazyInitialization = annoAttrs.getString("lazyInitialization");
if (StringUtils.hasText(lazyInitialization)) {
builder.addPropertyValue("lazyInitialization", lazyInitialization);
}
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
1.2.3 MapperScannerConfigurer
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// 佔位符替換
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
// 構造掃描器進行掃描包,批量構造mapper
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
if (StringUtils.hasText(lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
}
scanner.registerFilters();
// 執行掃描註冊
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
主要點就在於構造掃描器。掃描Mapper的BeanDefinition並註冊。
1.2.4 ClassPathMapperScanner
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner
繼承Spring的掃描工具類,覆蓋doScan完成自定義部分。
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 藉助spring的 ClassPathBeanDefinitionScanner 掃描出指定路徑下的所有Mapper的Bean定義
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 {
// 處理mapper bean定義
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
+ "' mapperInterface");
// 改變掃描到的Mapper原本的BeanDefinition,beanClass都使用MapperFactoryBean.class
// 目的是為了建立Mapper的代理物件
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
definition.setBeanClass(this.mapperFactoryBeanClass);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
// 是否顯示指定了SqlSessionFactory
boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory",
new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
LOGGER.warn(
() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate",
new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
LOGGER.warn(
() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
// 如果未顯示指定SqlSessionFactory,則啟用自動注入
if (!explicitFactoryUsed) {
LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
definition.setLazyInit(lazyInitialization);
}
}
這裡有兩個點:
- 掃描的Mapper BeanDefinition會將BeanClass統一替換成MapperFactory.class,是為了為每一個Mapper都建立代理類,而不是其介面對應型別的例項。
- 未直接指定SqlSessionFactory(往往使用過程不會指定),則會設定BeanDefinition的模式為自動注入,即Spring提供的不適用@Autowire、@Resource也能自動注入屬性,解決了自動注入SqlSessionFactory的問題。
執行過程
MapperFactoryBean負責Mapper例項的建立,來看看MapperFactoryBean這個類。
類圖如下:
- 繼承FactoryBean是為了自定義Mapper例項化的操作。
- 繼承SqlSessionDaoSupport是為了管理SqlSessionFactory。
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
private boolean addToConfig = true;
public MapperFactoryBean() {
// intentionally empty
}
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
/**
* 為mapper類建立統一的代理物件
* {@inheritDoc}
*/
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
...
}
獲取Mapper例項的邏輯和單獨使用Mapper的時候相同,獲取SqlSession,使用SqlSession為mapper建立代理物件。
getSqlSession()方法在父類SqlSessionDaoSupport中。
public abstract class SqlSessionDaoSupport extends DaoSupport {
private SqlSessionTemplate sqlSessionTemplate;
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
}
}
@SuppressWarnings("WeakerAccess")
protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
public SqlSession getSqlSession() {
return this.sqlSessionTemplate;
}
}
2.2 SqlSession例項建立
SqlSessionTemplate屬性就是在MapperFactoryBean例項化的時候自動注入進去的,我們看一看SqlSessionTemplate。實現了SqlSession,可以代表成一個SqlSession,主要還是為了管理SqlSession的代理類。
public class SqlSessionTemplate implements SqlSession, DisposableBean {
private final SqlSessionFactory sqlSessionFactory;
private final ExecutorType executorType;
// SqlSession代理類
private final SqlSession sqlSessionProxy;
// 異常翻譯器,將mybatis的異常轉換成Spring的異常
private final PersistenceExceptionTranslator exceptionTranslator;
}
來看下SqlSessionTemplate的構造方法。
sqlSessionProxy是真正的SqlSession代理類,SqlSession相關方法的實現,SqlSessionTemplate都是委託給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;
// 建立SqlSession代理類例項
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class }, new SqlSessionInterceptor());
}
重點在於SqlSession代理類,這個類使用了JDK的動態代理,來看看究竟攔截方法並做了什麼。
讓我們看看SqlSessionInterceptor
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 獲取真正的SqlSession例項
SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
// 執行SqlSession的方法(舉例:sqlSession.getMapper(XXX.class))
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// 如果SqlSession未處於spring事務,那設定未自動提交
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) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
SqlSessionInterceptor是SqlSessionTemplate的內部類,實現了InvocationHandler(JDK動態代理必備)。
SqlSessionTemplate偽裝成SqlSession,實現其介面,內部實現委託給SqlSessionProxy完成。
2.3 事務
事務控制著Connection,與Connection對應的則是SqlSession。看看mybatis-spring怎麼對接的Spring事務去控制SqlSession。
在上面SqlSessionInterceptor的invoke部分,獲取SqlSession,呼叫的是
SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
繼續看下內部如何實現
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
// 從spring 事務中獲取SqlSession
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);
// 將SqlSession註冊到 spring事務中
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
這裡簡單的提一點Spring事務,Spring會使用ThreadLocal儲存事務的資訊,在這裡mybatis=spring藉助TransactionSynchronizationManager.getResource/bindResource來獲取/繫結SqlSession。
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
SqlSessionHolder holder;
// 判斷當前同步是否啟用
if (TransactionSynchronizationManager.isSynchronizationActive()) {
Environment environment = sessionFactory.getConfiguration().getEnvironment();
// 如果是SpringManagedTransactionFactory則進行spring事務對接
if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
LOGGER.debug(() -> "Registering transaction synchronization for SqlSession [" + session + "]");
holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
// 繫結SqlSession資訊到當前執行緒中,key是SqlSessionFactory
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
TransactionSynchronizationManager
.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
holder.setSynchronizedWithTransaction(true);
holder.requested();
} else {
if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
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 {
LOGGER.debug(() -> "SqlSession [" + session
+ "] was not registered for synchronization because synchronization is not active");
}
}
到這裡可以看到,通過對接TransactionSynchronizationManager,實現了與Spring事務的對接。
通過ThreadLocal儲存了一個HashMap,key是SqlSessionFactory,value是SqlSession,代表一個執行緒在一個事務中一個數據源只能有一個Connection,事務中不能切換Connection,所以SqlSession將會被複用。
mybatis-spring事務總結一下:
對接事務就是通過對接Spring事務來管理SqlSession的建立/獲取/銷燬,
事務開啟時獲取SqlSession例項時使用TransactionSynchronizationManager獲取,一個執行緒一個map,key是SqlSessionFactory,value是SqlSession。
事務關閉、提交時,也是先刪除資源,再呼叫sqlSession的close方法,完成資源關閉。