1. 程式人生 > 實用技巧 >【Mybatis原始碼解析】-mybatis-spring原理解析

【Mybatis原始碼解析】-mybatis-spring原理解析

mybatis-spring原理解析

​ 沒有spring使用Mybatis的步驟是

1:建立SqlSessionFactoryBuilder

2: 通過SqlSessionFactoryBuilder建立要給SqlSessionFactory

3: 建立SqlSession

4: 獲取Mapper的代理物件

5:執行獲取結果

很多都是重複步驟,可以交給spring去管理

mybatis-spring就是幫助處理這些工作,

Mybatis-spring主要做的內容包含:

  1. mybatis相關類 “Spring”化,都註冊到Spring 容器中,對mapper額外提供批量掃描功能。
  2. 事務對接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開始介紹。

過程概覽:

  1. MapperScan註解通過@Import(MapperScannerRegistrar.class),匯入了MapperScannerRegistrar類
  2. MapperScannerRegistrar獲得MapperScan註解上的值(主要是basePackages),構造MapperScannerConfigurer的BeanDefinition並註冊。
  3. postProcessBeanDefinitionRegistry回撥進行掃描動作,例項化臨時的ClassPathMapperScanner,藉助其完成掃描註冊。
  4. 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);
  }
}

這裡有兩個點:

  1. 掃描的Mapper BeanDefinition會將BeanClass統一替換成MapperFactory.class,是為了為每一個Mapper都建立代理類,而不是其介面對應型別的例項。
  2. 未直接指定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方法,完成資源關閉。