1. 程式人生 > >精盡MyBatis原始碼分析 - MyBatis-Spring 原始碼分析

精盡MyBatis原始碼分析 - MyBatis-Spring 原始碼分析

> 該系列文件是本人在學習 Mybatis 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋([Mybatis原始碼分析 GitHub 地址](https://github.com/liu844869663/mybatis-3)、[Mybatis-Spring 原始碼分析 GitHub 地址](https://github.com/liu844869663/spring)、[Spring-Boot-Starter 原始碼分析 GitHub 地址](https://github.com/liu844869663/spring-boot-starter))進行閱讀 > > MyBatis 版本:3.5.2 > > MyBatis-Spring 版本:2.0.3 > > MyBatis-Spring-Boot-Starter 版本:2.1.4 在前面的一系列文件中對整個 MyBatis 框架進行了分析,相信你對 MyBatis 有了一個更加深入的瞭解。在使用它的過程中,需要自己建立 SqlSessionFactory 和 SqlSession,然後獲取到 Mapper 介面的動態代理物件,執行資料庫相關操作,對這些物件的管理並不是那麼簡單。我們通常會結合 Spring 來使用 MyBatis,將這些物件作為 Spring Bean 注入到 Spring 容器,也允許參與到 Spring 的事務管理之中 Spring 官方並沒有提供對 MyBatis3 的整合方案,於是在 MyBatis 社群將對 Spring 的整合作為一個 MyBatis 子專案 [MyBatis-Spring](https://github.com/mybatis/spring),幫助你將 MyBatis 程式碼無縫地整合到 Spring 中,那麼我們一起來看看這個子專案是如何整合到 Spring 中的 在開始讀這篇文件之前,需要對 Spring 有一定的瞭解,可以結合我的原始碼註釋([Mybatis-Spring 原始碼分析 GitHub 地址](https://github.com/liu844869663/mybatis-spring))進行閱讀,[MyBatis-Spring官方文件](http://mybatis.org/spring/zh/index.html) ## 簡述 主要涉及到的幾個類: - `org.mybatis.spring.SqlSessionFactoryBean`:實現 FactoryBean、InitializingBean、ApplicationListener 介面,負責構建一個 SqlSessionFactory 物件 - `org.mybatis.spring.mapper.MapperFactoryBean`:實現 FactoryBean 介面,繼承 SqlSessionDaoSupport 抽象類,Mapper 介面對應 Spring Bean 物件,用於返回對應的動態代理物件 - `org.mybatis.spring.support.SqlSessionDaoSupport`抽象類,繼承了 DaoSupport 抽象類,用於構建一個 SqlSessionTemplate 物件 - `org.mybatis.spring.mapper.MapperScannerConfigurer`:實現了BeanDefinitionRegistryPostProcessor、InitializingBean介面,ApplicationContextAware、BeanNameAware介面,用於掃描Mapper介面,藉助`ClassPathMapperScanner`掃描器對Mapper介面的BeanDefinition物件(Spring Bean 的前身)進行修改 - `org.mybatis.spring.mapper.ClassPathMapperScanner`:繼承了ClassPathBeanDefinitionScanner抽象類,負責執行掃描,修改掃描到的 Mapper 介面的 BeanDefinition 物件(Spring Bean的前身),將其 Bean Class 修改為 MapperFactoryBean,從而在 Spring 初始化該 Bean 的時候,會初始化成 `MapperFactoryBean` 型別,實現建立 Mapper 動態代理物件 - `org.mybatis.spring.annotation.MapperScannerRegistrar`:實現 ImportBeanDefinitionRegistrar、ResourceLoaderAware 介面 作為`@MapperScann` 註解的註冊器,根據註解資訊註冊一個 `MapperScannerConfigurer` 物件,用於掃描 Mapper 介面 - `org.mybatis.spring.config.MapperScannerBeanDefinitionParser`:實現 BeanDefinitionParser 介面,`
` 的解析器,和`MapperScannerRegistrar`的實現邏輯一樣 - `org.mybatis.spring.SqlSessionTemplate`:實現 SqlSession 和 DisposableBean 介面,SqlSession 操作模板實現類,承擔 SqlSessionFactory 和 SqlSession 的職責 - `org.mybatis.spring.SqlSessionUtils`:SqlSession 工具類,負責處理 MyBatis SqlSession 的生命週期,藉助 Spring 的 TransactionSynchronizationManager 事務管理器管理 SqlSession 對像 大致邏輯如下: 1. 通過配置 `MapperScannerConfigurer` 的 Spring Bean,它會結合 `ClassPathMapperScanner` 掃描器,對指定包路徑下的 Mapper 介面對應 BeanDefinition 物件(Spring Bean 的前身)進行修改,將其 Bean Class 修改為 `MapperFactoryBean` 型別,從而在 Spring 初始化該 Bean 的時候,會初始化成 `MapperFactoryBean` 物件,實現建立 Mapper 動態代理物件 2. 在 `MapperFactoryBean` 物件中`getObject()`中,根據 `SqlSessionTemplate` 物件為該 Mapper 介面建立一個動態代理物件,也就是說在我們注入該 Mapper 介面時,實際注入的是 Mapper 介面對應的動態代理物件 3. `SqlSessionTemplate` 物件中,承擔 SqlSessionFactory 和 SqlSession 的職責,結合 Spring 的事務體系進行處理 ## 配置示例 ```xml
``` - 上面會建立`DruidDataSource`資料來源,`SqlSessionFactoryBean`和`MapperScannerConfigurer`物件 ## SqlSessionFactoryBean `org.mybatis.spring.SqlSessionFactoryBean`:實現 FactoryBean、InitializingBean、ApplicationListener 介面,負責構建一個 SqlSessionFactory 物件 關於Spring的`FactoryBean`機制,不熟悉的先去了解一下,大致就是Spring在注入該型別的Bean時,呼叫的是它的`getObject()`方法 ### 構造方法 ```java public class SqlSessionFactoryBean implements FactoryBean, InitializingBean, ApplicationListener { private static final ResourcePatternResolver RESOURCE_PATTERN_RESOLVER = new PathMatchingResourcePatternResolver(); private static final MetadataReaderFactory METADATA_READER_FACTORY = new CachingMetadataReaderFactory(); /** * 指定的 mybatis-config.xml 路徑的資源 */ private Resource configLocation; private Configuration configuration; /** * 指定 XML 對映檔案路徑的資源陣列 */ private Resource[] mapperLocations; /** * 資料來源 */ private DataSource dataSource; private TransactionFactory transactionFactory; private Properties configurationProperties; private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); /** * SqlSession 工廠,預設為 DefaultSqlSessionFactory */ private SqlSessionFactory sqlSessionFactory; // EnvironmentAware requires spring 3.1 private String environment = SqlSessionFactoryBean.class.getSimpleName(); private boolean failFast; private Interceptor[] plugins; private TypeHandler[] typeHandlers; private String typeHandlersPackage; @SuppressWarnings("rawtypes") private Class defaultEnumTypeHandler; private Class[] typeAliases; private String typeAliasesPackage; private Class typeAliasesSuperType; private LanguageDriver[] scriptingLanguageDrivers; private Class defaultScriptingLanguageDriver; // issue #19. No default provider. private DatabaseIdProvider databaseIdProvider; private Class vfs; private Cache cache; private ObjectFactory objectFactory; private ObjectWrapperFactory objectWrapperFactory; } ``` 可以看到上面定義的各種屬性,這裡就不一一解釋了,根據名稱可以知道屬性的作用 ### afterPropertiesSet方法 `afterPropertiesSet()`方法,實現的 InitializingBean 介面,在 Spring 容器中,初始化該 Bean 時,會呼叫該方法,方法如下: ```java @Override public void afterPropertiesSet() throws Exception { // 校驗 dataSource 資料來源不能為空 notNull(dataSource, "Property 'dataSource' is required"); // 校驗 sqlSessionFactoryBuilder 構建器不能為空,上面預設 new 一個物件 notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required"); // configuration 和 configLocation 有且只有一個不為空 state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null), "Property 'configuration' and 'configLocation' can not specified with together"); // 初始化 SqlSessionFactory this.sqlSessionFactory = buildSqlSessionFactory(); } ``` 1. 校驗 dataSource 資料來源不能為空,所以配置該Bean時,必須配置一個數據源 2. 校驗 sqlSessionFactoryBuilder 構建器不能為空,上面預設 new 一個物件 3. configuration 和 configLocation 有且只有一個不為空 4. 呼叫`buildSqlSessionFactory()`方法,初始化 `SqlSessionFactory` ### buildSqlSessionFactory方法 `buildSqlSessionFactory()`方法,根據配置資訊構建一個`SqlSessionFactory`例項,方法如下: ```java protected SqlSessionFactory buildSqlSessionFactory() throws Exception { final Configuration targetConfiguration; // 初始化 Configuration 全域性配置物件 XMLConfigBuilder xmlConfigBuilder = null; if (this.configuration != null) { // 如果已存在 Configuration 物件 targetConfiguration = this.configuration; if (targetConfiguration.getVariables() == null) { targetConfiguration.setVariables(this.configurationProperties); } else if (this.configurationProperties != null) { targetConfiguration.getVariables().putAll(this.configurationProperties); } } else if (this.configLocation != null) { // 否則,如果配置了 mybatis-config.xml 配置檔案 xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties); targetConfiguration = xmlConfigBuilder.getConfiguration(); } else { LOGGER.debug(() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration"); // 否則,建立一個 Configuration 物件 targetConfiguration = new Configuration(); Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables); } /* * 如果配置了 ObjectFactory(例項工廠)、ObjectWrapperFactory(ObjectWrapper工廠)、VFS(虛擬檔案系統) * 則分別往 Configuration 全域性配置物件設定 */ Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory); Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory); Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl); /* * 如果配置了需要設定別名的包路徑,則掃描該包路徑下的 Class 物件 * 往 Configuration 全域性配置物件的 TypeAliasRegistry 別名登錄檔進行註冊 */ 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); } /* * 如果單獨配置了需要設定別名的 Class 物件 * 則將其往 Configuration 全域性配置物件的 TypeAliasRegistry 別名登錄檔註冊 */ if (!isEmpty(this.typeAliases)) { Stream.of(this.typeAliases).forEach(typeAlias -> { targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias); LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'"); }); } // 往 Configuration 全域性配置物件新增 Interceptor 外掛 if (!isEmpty(this.plugins)) { Stream.of(this.plugins).forEach(plugin -> { targetConfiguration.addInterceptor(plugin); LOGGER.debug(() -> "Registered plugin: '" + plugin + "'"); }); } // 掃描包路徑,往 Configuration 全域性配置物件新增 TypeHandler 型別處理器 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); } // 往 Configuration 全域性配置物件新增 TypeHandler 型別處理器 if (!isEmpty(this.typeHandlers)) { Stream.of(this.typeHandlers).forEach(typeHandler -> { targetConfiguration.getTypeHandlerRegistry().register(typeHandler); LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'"); }); } // 設定預設的列舉型別處理器 targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler); // 往 Configuration 全域性配置物件新增 LanguageDriver 語言驅動 if (!isEmpty(this.scriptingLanguageDrivers)) { Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> { targetConfiguration.getLanguageRegistry().register(languageDriver); LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'"); }); } // 設定預設的 LanguageDriver 語言驅動 Optional.ofNullable(this.defaultScriptingLanguageDriver) .ifPresent(targetConfiguration::setDefaultScriptingLanguage); // 設定當前資料來源的資料庫 id 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); } } // 新增 Cache 快取 Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache); if (xmlConfigBuilder != null) { try { // 如果配置了 mybatis-config.xml 配置檔案,則初始化 MyBatis 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(); } } // 設定 Environment 環境資訊 targetConfiguration.setEnvironment(new Environment(this.environment, this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory, this.dataSource)); // 如果配置了 XML 對映檔案的路徑 if (this.mapperLocations != null) { if (this.mapperLocations.length == 0) { LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found."); } else { /* * 遍歷所有的 XML 對映檔案 */ for (Resource mapperLocation : this.mapperLocations) { if (mapperLocation == null) { continue; } try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments()); // 解析 XML 對映檔案 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."); } // 通過構建器建立一個 DefaultSqlSessionFactory 物件 return this.sqlSessionFactoryBuilder.build(targetConfiguration); } ``` 方法有點長,主要是通過配置資訊建立一個 Configuration 物件,然後構建一個 DefaultSqlSessionFactory 物件 1. 初始化 `Configuration` 全域性配置物件 1. 如果已存在 Configuration 物件,則直接使用該物件 2. 否則,如果配置了 mybatis-config.xml 配置檔案,則建立一個 XMLConfigBuilder 物件,待解析 3. 否則,建立一個 Configuration 物件 2. 往 Configuration 物件中設定相關配置屬性 3. 如果是`1.2`步生成的 Configuration 物件,那麼呼叫 `XMLConfigBuilder` 的 `parse()` 方法進行解析,初始化 MyBatis,在[**《MyBatis 初始化(一)之載入mybatis-config.xml》**](https://www.cnblogs.com/lifullmoon/p/14015009.html)中分析過 4. 如果配置了 XML 對映檔案的路徑`mapperLocations`,則進行遍歷依次解析,通過建立`XMLMapperBuilder`物件,呼叫其`parse()`方法進行解析,在[**《MyBatis 初始化(二)之載入Mapper介面與對映檔案》**](https://www.cnblogs.com/lifullmoon/p/14015046.html)的**XMLMapperBuilder**小節中分析過 5. 通過`SqlSessionFactoryBuilder`構建器建立一個`DefaultSqlSessionFactory`物件 > 在 MyBatis-Spring 專案中,除了通過 mybatis-config.xml 配置檔案配置 Mapper 介面的方式以外,還提供了幾種配置方法,這裡的`SqlSessionFactoryBean.mapperLocations`也算一種 > > 這樣在 Spring 中,Mapper 介面和對應的 XML 對映檔名稱可以不一致,檔案中裡面配置 `namepace` 正確就可以了 ### getObject方法 `getObject()`方法,在 Spring 容器中,注入當前 Bean 時呼叫該方法,也就是返回 DefaultSqlSessionFactory 物件,方法如下: ```java @Override public SqlSessionFactory getObject() throws Exception { if (this.sqlSessionFactory == null) { // 如果為空則初始化 sqlSessionFactory afterPropertiesSet(); } // 返回 DefaultSqlSessionFactory 物件 return this.sqlSessionFactory; } ``` ### onApplicationEvent方法 `onApplicationEvent(ApplicationEvent event)`方法,監聽 ContextRefreshedEvent 事件,如果還存在未初始化完成的 MapperStatement 們,則再進行解析,方法如下: ```java @Override public void onApplicationEvent(ApplicationEvent event) { // 如果配置了需要快速失敗,並且監聽到了 Spring 容器初始化完成事件 if (failFast && event instanceof ContextRefreshedEvent) { // fail-fast -> check all statements are completed // 將 MyBatis 中還未完全解析的物件,在這裡再進行解析 this.sqlSessionFactory.getConfiguration().getMappedStatementNames(); } } ``` ## MapperFactoryBean `org.mybatis.spring.mapper.MapperFactoryBean`:實現 FactoryBean 介面,繼承 SqlSessionDaoSupport 抽象類,Mapper 介面對應 Spring Bean 物件,用於返回對應的動態代理物件 ### 構造方法 ```java public class MapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean { /** * Mapper 介面 */ private Class mapperInterface; /** * 是否新增到 {@link Configuration} 中,預設為 true */ private boolean addToConfig = true; public MapperFactoryBean(Class mapperInterface) { this.mapperInterface = mapperInterface; } } ``` - `mapperInterface`:對應的Mapper介面 - `addToConfig`:是否新增到 Configuration 中,預設為 true ### getObject方法 `getObject()`方法,在 Spring 容器中,注入當前 Bean 時呼叫該方法,也就是返回 Mapper 介面對應的動態代理物件,方法如下: ```java @Override public T getObject() throws Exception { // getSqlSession() 方法返回 SqlSessionTemplate 物件 return getSqlSession().getMapper(this.mapperInterface); } ``` `getSqlSession()`方法在`SqlSessionDaoSupport`中定義,返回的是`SqlSessionTemplate`物件,後續會講到 可以先暫時理解為就是返回一個 DefaultSqlSession,獲取`mapperInterface`Mapper介面對應的動態代理物件 > 這也就是為什麼在Spring中注入Mapper介面Bean時,我們可以直接呼叫它的方法 ### checkDaoConfig方法 `checkDaoConfig()`方法,校驗該 Mapper 介面是否被初始化並新增到 Configuration 中 ```java @Override protected void checkDaoConfig() { // 校驗 sqlSessionTemplate 非空 super.checkDaoConfig(); // 校驗 mapperInterface 非空 notNull(this.mapperInterface, "Property 'mapperInterface' is required"); /* * 如果該 Mapper 介面沒有被解析至 Configuration,則對其進行解析 */ Configuration configuration = getSqlSession().getConfiguration(); if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) { try { // 將該 Mapper 介面新增至 Configuration,會對該介面進行一系列的解析 configuration.addMapper(this.mapperInterface); } catch (Exception e) { logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e); throw new IllegalArgumentException(e); } finally { ErrorContext.instance().reset(); } } } ``` 1. 校驗 `sqlSessionTemplate` 非空 2. 校驗 `mapperInterface` 非空 3. 如果該 Mapper 介面沒有被解析至 Configuration,則對其進行解析 因為繼承了`DaoSupport`抽象類,實現了 InitializingBean 介面,在 afterPropertiesSet() 方法中會呼叫`checkDaoConfig()`方法 ## SqlSessionDaoSupport `org.mybatis.spring.support.SqlSessionDaoSupport`抽象類,繼承了 DaoSupport 抽象類,用於構建一個 SqlSessionTemplate 物件,程式碼如下: ```java 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 final SqlSessionFactory getSqlSessionFactory() { return (this.sqlSessionTemplate != null ? this.sqlSessionTemplate.getSqlSessionFactory() : null); } public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) { this.sqlSessionTemplate = sqlSessionTemplate; } public SqlSession getSqlSession() { return this.sqlSessionTemplate; } public SqlSessionTemplate getSqlSessionTemplate() { return this.sqlSessionTemplate; } @Override protected void checkDaoConfig() { notNull(this.sqlSessionTemplate, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required"); } } ``` - `setSqlSessionFactory(SqlSessionFactory sqlSessionFactory)`方法,將 SqlSessionFactory 構建成我們需要的 SqlSessionTemplate 物件,該物件在後續講到 - `checkDaoConfig()`方法,校驗 SqlSessionTemplate 非空 ## MapperScannerConfigurer `org.mybatis.spring.mapper.MapperScannerConfigurer`:實現了`BeanDefinitionRegistryPostProcessor`、InitializingBean介面,ApplicationContextAware、BeanNameAware介面 用於掃描Mapper介面,藉助`ClassPathMapperScanner`修改Mapper介面的BeanDefinition物件(Spring Bean 的前身),將Bean的Class物件修改為`MapperFactoryBean`型別 那麼在Spring初始化該Bean的時候,會初始化成MapperFactoryBean型別 ### 構造方法 ```java public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware { /** * Mapper 介面的包路徑 */ private String basePackage; /** * 是否要將介面新增到 Configuration 全域性配置物件中 */ private boolean addToConfig = true; private String lazyInitialization; private SqlSessionFactory sqlSessionFactory; private SqlSessionTemplate sqlSessionTemplate; private String sqlSessionFactoryBeanName; private String sqlSessionTemplateBeanName; private Class annotationClass; private Class markerInterface; private Class mapperFactoryBeanClass; private ApplicationContext applicationContext; private String beanName; private boolean processPropertyPlaceHolders; private BeanNameGenerator nameGenerator; private String defaultScope; @Override public void afterPropertiesSet() throws Exception { notNull(this.basePackage, "Property 'basePackage' is required"); } } ``` 在**SqlSessionFactoryBean**小節的示例中可以看到,定義了`basePackage`和`sqlSessionFactoryBeanName`兩個屬性 - `basePackage`:Mapper 介面的包路徑 - `addToConfig`:是否要將介面新增到 Configuration 全域性配置物件中 - `sqlSessionFactoryBeanName`:SqlSessionFactory的Bean Name 在`afterPropertiesSet()`方法中會校驗`basePackage`非空 > 在 MyBatis-Spring 專案中,除了通過 mybatis-config.xml 配置檔案配置 Mapper 介面的方式以外,還提供了幾種配置方法,這裡的配置 `basePackage` 屬性也算一種 > > 這樣在 Spring 中,Mapper 介面和對應的 XML 對映檔名稱可以不一致,檔案中裡面配置 `namepace` 正確就可以了 ### postProcessBeanDefinitionRegistry方法 `postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)`方法,在 BeanDefinitionRegistry 完成後進行一些處理 這裡會藉助`ClassPathMapperScanner`掃描器,掃描指定包路徑下的 Mapper 介面,方法如下: ```java @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { if (this.processPropertyPlaceHolders) { // 處理屬性中的佔位符 processPropertyPlaceHolders(); } // 建立一個 Bean 掃描器 ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); // 是否要將 Mapper 介面新增到 Configuration 全域性配置物件中 scanner.setAddToConfig(this.addToConfig); scanner.setAnnotationClass(this.annotationClass); scanner.setMarkerInterface(this.markerInterface); scanner.setSqlSessionFactory(this.sqlSessionFactory); scanner.setSqlSessionTemplate(this.sqlSessionTemplate); // 設定 SqlSessionFactory 的 BeanName 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)); } if (StringUtils.hasText(defaultScope)) { scanner.setDefaultScope(defaultScope); } // 新增幾個過濾器 scanner.registerFilters(); scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); } ``` 1. 如果需要處理屬性中的佔位符,則呼叫`processPropertyPlaceHolders()`方法 2. 建立一個 Bean 掃描器 `ClassPathMapperScanner` 物件 3. 設定一些 Mapper 介面掃描器的屬性,例如`addToConfig`、`sqlSessionFactoryBeanName` 4. 呼叫掃描器的`registerFilters()`方法,新增幾個過濾器,過濾指定路徑下的 Mapper 介面 5. 呼叫其`scan`方法,開始掃描 `basePackage`路徑下的 Mapper 介面 ## ClassPathMapperScanner `org.mybatis.spring.mapper.ClassPathMapperScanner`:繼承了ClassPathBeanDefinitionScanner抽象類 負責執行掃描,修改掃描到的 Mapper 介面的 BeanDefinition 物件(Spring Bean的前身),將其 Bean Class 修改為 MapperFactoryBean,從而在 Spring 初始化該 Bean 的時候,會初始化成 `MapperFactoryBean` 型別,實現建立 Mapper 動態代理物件 ### 構造方法 ```java public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner { private static final Logger LOGGER = LoggerFactory.getLogger(ClassPathMapperScanner.class); /** * 是否要將 Mapper 介面新增到 Configuration 全域性配置物件中 */ private boolean addToConfig = true; private boolean lazyInitialization; private SqlSessionFactory sqlSessionFactory; private SqlSessionTemplate sqlSessionTemplate; private String sqlSessionTemplateBeanName; /** * SqlSessionFactory Bean 的名稱 */ private String sqlSessionFactoryBeanName; private Class annotationClass; private Class markerInterface; /** * 將 Mapper 介面轉換成 MapperFactoryBean 物件 */ private Class mapperFactoryBeanClass = MapperFactoryBean.class; private String defaultScope; } ``` - `basePackage`:Mapper 介面的包路徑 - `addToConfig`:是否要將介面新增到 Configuration 全域性配置物件中 - `sqlSessionFactoryBeanName`:SqlSessionFactory的Bean Name 上面這幾個屬性在 MapperScannerConfigurer 建立該物件的時候會進行賦值 ### registerFilters方法 `registerFilters()`方法,新增幾個過濾器,用於掃描 Mapper 介面的過程中過濾出我們需要的 Mapper 介面,方法如下: ```java public void registerFilters() { // 標記是否接受所有的 Mapper 介面 boolean acceptAllInterfaces = true; // if specified, use the given annotation and / or marker interface // 如果配置了註解,則掃描有該註解的 Mapper 介面 if (this.annotationClass != null) { addIncludeFilter(new AnnotationTypeFilter(this.annotationClass)); acceptAllInterfaces = false; } // override AssignableTypeFilter to ignore matches on the actual marker interface // 如果配置了某個介面,則也需要掃描該介面 if (this.markerInterface != null) { addIncludeFilter(new AssignableTypeFilter(this.markerInterface) { @Override protected boolean matchClassName(String className) { return false; } }); acceptAllInterfaces = false; } if (acceptAllInterfaces) { // default include filter that accepts all classes // 如果上面兩個都沒有配置,則接受所有的 Mapper 介面 addIncludeFilter((metadataReader, metadataReaderFactory) -> true); } // exclude package-info.java // 排除 package-info.java 檔案 addExcludeFilter((metadataReader, metadataReaderFactory) -> { String className = metadataReader.getClassMetadata().getClassName(); return className.endsWith("package-info"); }); } ``` 1. 標記是否接受所有的介面 2. 如果配置了註解,則新增一個過濾器,需要有該註解的介面 3. 如果配置了某個介面,則新增一個過濾器,必須是該介面 4. 如果沒有第`2`、`3`步,則新增一個過濾器,接收所有的介面 5. 新增過濾器,排除 package-info.java 檔案 ### doScan方法 `doScan(String... basePackages)`方法,掃描指定包路徑,根據上面的過濾器,獲取路徑下對應的 BeanDefinition 集合,進行一些後置處理,方法如下: ```java @Override public Set doScan(String... basePackages) { // 掃描指定包路徑,根據上面的過濾器,獲取到路徑下 Class 物件的 BeanDefinition 物件 Set 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; } ``` 1. 掃描指定包路徑,根據上面的過濾器,獲取到路徑下符合條件的 Resource 資源,並生成對應的 `BeanDefinition` 物件註冊到 BeanDefinitionRegistry 登錄檔中,並返回 2. 如果不為空,則呼叫 `processBeanDefinitions` 方法,進行一些後置處理 ### processBeanDefinitions方法 `processBeanDefinitions(Set beanDefinitions)`方法,對指定包路徑下符合條件的`BeanDefinition` 物件進行一些處理,修改其 Bean Class 為 `MapperFactoryBean` 型別,方法如下: ```java private void processBeanDefinitions(Set beanDefinitions) { AbstractBeanDefinition definition; // <1> 獲取 BeanDefinition 登錄檔,然後開始遍歷 BeanDefinitionRegistry registry = getRegistry(); for (BeanDefinitionHolder holder : beanDefinitions) { definition = (AbstractBeanDefinition) holder.getBeanDefinition(); boolean scopedProxy = false; if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) { // 獲取被裝飾的 BeanDefinition 物件 definition = (AbstractBeanDefinition) Optional .ofNullable(((RootBeanDefinition) definition).getDecoratedDefinition()) .map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> new IllegalStateException( "The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]")); scopedProxy = true; } // <2> 獲取對應的 Bean 的 Class 名稱,也就是 Mapper 介面的 Class 物件 String beanClassName = definition.getBeanClassName(); LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName + "' mapperInterface"); // the mapper interface is the original class of the bean // but, the actual class of the bean is MapperFactoryBean // <3> 往構造方法的引數列表中新增一個引數,為當前 Mapper 介面的 Class 物件 definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59 /* * <4> 修改該 Mapper 介面的 Class物件 為 MapperFactoryBean.class * 這樣一來當你注入該 Mapper 介面的時候,實際注入的是 MapperFactoryBean 物件,構造方法的入參就是 Mapper 介面 */ definition.setBeanClass(this.mapperFactoryBeanClass); // <5> 新增 addToConfig 屬性 definition.getPropertyValues().add("addToConfig", this.addToConfig); boolean explicitFactoryUsed = false; // <6> 開始新增 sqlSessionFactory 或者 sqlSessionTemplate 屬性 /* * 1. 如果設定了 sqlSessionFactoryBeanName,則新增 sqlSessionFactory 屬性,實際上配置的是 SqlSessionFactoryBean 物件 * 2. 否則,如果配置了 sqlSessionFactory 物件,則新增 sqlSessionFactory 屬性 */ 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; } /* * 1. 如果配置了 sqlSessionTemplateBeanName,則新增 sqlSessionTemplate 屬性 * 2. 否則,如果配置了 sqlSessionTemplate 物件,則新增 sqlSessionTemplate 屬性 * SqlSessionFactory 和 SqlSessionTemplate 都配置了則會列印一個警告 */ if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) { if (explicitFactoryUsed) { // 如果上面已經清楚的使用了 SqlSessionFactory,則列印一個警告 LOGGER.warn(() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored."); } // 新增 sqlSessionTemplate 屬性 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); if (scopedProxy) { // 已經封裝過的則直接執行下一個 continue; } if (ConfigurableBeanFactory.SCOPE_SINGLETON.equals(definition.getScope()) && defaultScope != null) { definition.setScope(defaultScope); } /* * 如果不是單例模式,預設是 * 將 BeanDefinition 在封裝一層進行註冊 */ if (!definition.isSingleton()) { BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true); if (registry.containsBeanDefinition(proxyHolder.getBeanName())) { registry.removeBeanDefinition(proxyHolder.getBeanName()); } registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition()); } } } ``` 1. 獲取 BeanDefinition 登錄檔,然後開始遍歷 2. 獲取對應的 Bean 的 Class 名稱,也就是 Mapper 介面的 Class 物件 3. 往構造方法的引數列表中新增一個引數,為當前 Mapper 介面的名稱,因為 MapperFactoryBean 的構造方法的入參就是 Mapper 介面 4. 修改該 Mapper 介面的 Class 物件 為 `MapperFactoryBean`,根據第`3`步則會為該 Mapper 介面建立一個對應的 `MapperFactoryBean` 物件了 5. 新增 `addToConfig` 屬性,Mapper 是否新增到 Configuration 中 6. 開始新增 `sqlSessionFactory` 或者 `sqlSessionTemplate` 屬性 1. 如果設定了 sqlSessionFactoryBeanName,則新增 sqlSessionFactory 屬性,實際上配置的是 SqlSessionFactoryBean 物件, 否則,如果配置了 sqlSessionFactory 物件,則新增 sqlSessionFactory 屬性 在 `SqlSessionDaoSupport` 的 `setSqlSessionFactory(SqlSessionFactory sqlSessionFactory)`方法中你會發現建立的就是`SqlSessionTemplate` 物件 2. 如果配置了 sqlSessionTemplateBeanName,則新增 sqlSessionTemplate 屬性 否則,如果配置了 sqlSessionTemplate 物件,則新增 sqlSessionTemplate 屬性 3. 上面沒有找到對應的 SqlSessionFactory,則設定通過型別注入 該方法的處理邏輯大致如上描述,主要做了一下幾個事: - 將 Mapper 介面的 BeanDefinition 物件的 beanClass 屬性修改成了`MapperFactoryBean`的 Class 物件 - 添加了一個入參為 Mapper 介面,這樣初始化的 Spring Bean 就是該 Mapper 介面對應的 `MapperFactoryBean` 物件了 - 新增`MapperFactoryBean` 物件的`sqlSessionTemplate`屬性 ## @MapperScan註解 `org.mybatis.spring.annotation.@MapperScan` 註解,指定需要掃描的包,將包中符合條件的 Mapper 介面,註冊成 `beanClass` 為 MapperFactoryBean 的 BeanDefinition 物件,從而實現建立 Mapper 物件 我們程式碼如下: ```java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(MapperScannerRegistrar.class) @Repeatable(MapperScans.class) public @interface MapperScan { String[] value() default {}; String[] basePackages() default {}; Class[] basePackageClasses() default {}; Class nameGenerator() default BeanNameGenerator.class; Class annotationClass() default Annotation.class; Class markerInterface() default Class.class; String sqlSessionTemplateRef() default ""; String sqlSessionFactoryRef() default ""; Class factoryBean() default MapperFactoryBean.class; String lazyInitialization() default ""; String defaultScope() default AbstractBeanDefinition.SCOPE_DEFAULT; } ``` - `value`和`basePackage`都是指定 Mapper 介面的包路徑 - `@Import(MapperScannerRegistrar.class)`,該註解負責資源的匯入,如果匯入的是一個 Java 類,例如此處為 MapperScannerRegistrar 類,Spring 會將其註冊成一個 Bean 物件 ### MapperScannerRegistrar `org.mybatis.spring.annotation.MapperScannerRegistrar`:實現 ImportBeanDefinitionRegistrar、ResourceLoaderAware 介面 作為`@MapperScann` 註解的註冊器,根據註解資訊註冊一個 `MapperScannerConfigurer` 物件,用於掃描 Mapper 介面 #### registerBeanDefinitions方法 `registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)`方法 ```java @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { // 獲得 @MapperScan 註解資訊 AnnotationAttributes mapperScanAttrs = AnnotationAttributes .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName())); if (mapperScanAttrs != null) { registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry, // 生成 Bean 的名稱,'org.springframework.core.type.AnnotationMetadata#MapperScannerRegistrar#0' generateBaseBeanName(importingClassMetadata, 0)); } } ``` 1. 獲得 @MapperScan 註解資訊 2. 呼叫`generateBaseBeanName`方法,為`MapperScannerConfigurer`生成一個beanName:`org.springframework.core.type.AnnotationMetadata#MapperScannerRegistrar#0` 3. 呼叫`registerBeanDefinitions`過載方法,註冊一個型別為`MapperScannerConfigurer`的 BeanDefinition 物件 #### registerBeanDefinitions過載方法 `registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName)`方法 ```java void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) { // 建立一個 BeanDefinition 構建器,用於構建 MapperScannerConfigurer 的 BeanDefinition 物件 BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); // 新增是否處理屬性中的佔位符屬性 builder.addPropertyValue("processPropertyPlaceHolders", true); /* * 依次添加註解中的配置屬性 */ Class 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 generatorClass = annoAttrs.getClass("nameGenerator"); if (!BeanNameGenerator.class.equals(generatorClass)) { builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass)); } Class 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")); } /* * 獲取到配置的 Mapper 介面的包路徑 */ List 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())); /* * 如果沒有 Mapper 介面的包路徑,則預設使用註解類所在的包路徑 */ if (basePackages.isEmpty()) { basePackages.add(getDefaultBasePackage(annoMeta)); } String lazyInitialization = annoAttrs.getString("lazyInitialization"); if (StringUtils.hasText(lazyInitialization)) { builder.addPropertyValue("lazyInitialization", lazyInitialization); } String defaultScope = annoAttrs.getString("defaultScope"); if (!AbstractBeanDefinition.SCOPE_DEFAULT.equals(defaultScope)) { builder.addPropertyValue("defaultScope", defaultScope); } // 新增 Mapper 介面的包路徑屬性 builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages)); // 往 BeanDefinitionRegistry 登錄檔註冊 MapperScannerConfigurer 對應的 BeanDefinition 物件 registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); } ``` 1. 建立一個 BeanDefinition 構建器,用於構建 `MapperScannerConfigurer` 的 BeanDefinition 物件 2. 新增是否處理屬性中的佔位符屬性`processPropertyPlaceHolders` 3. 依次新增`@MapperScan`註解中的配置屬性,例如:`sqlSessionFactoryBeanName`和`basePackages` 4. 往 BeanDefinitionRegistry 登錄檔註冊 `MapperScannerConfigurer` 型別的 BeanDefinition 物件 這樣在 Spring 容器初始化的過程中,會建立一個 MapperScannerConfigurer 物件,然後回到**MapperScannerConfigurer**的**postProcessBeanDefinitionRegistry方法**中,對包路徑下的 Mapper 介面進行解析,前面已經分析過了 #### RepeatingRegistrar內部類 MapperScannerRegistrar的內部類,程式碼如下: ```java static class RepeatingRegistrar extends MapperScannerRegistrar { /** * {@inheritDoc} */ @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { AnnotationAttributes mapperScansAttrs = AnnotationAttributes .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScans.class.getName())); if (mapperScansAttrs != null) { // 獲取 MapperScan 註解陣列 AnnotationAttributes[] annotations = mapperScansAttrs.getAnnotationArray("value"); /* * 依次處理每個 MapperScan 註解 */ for (int i = 0; i < annotations.length; i++) { registerBeanDefinitions(importingClassMetadata, annotations[i], registry, generateBaseBeanName(importingClassMetadata, i)); } } } } ``` - 可以回到`@MapperScan`註解上面的`@Repeatable(MapperScans.class)`資訊,可以看到如果同一個類上面定義多個`@MapperScan`註解,則會生成對應的`@MapperScans`註解 - RepeatingRegistrar 用於處理`@MapperScans`註解,依次處理`@MapperScan`註解的資訊 - 和 MapperScannerRegistrar 一樣的處理方式,不過生成的多個 MapperScannerConfigurer 對應的 beanName 的字尾不一樣 ## 自定義
標籤 除了配置 MapperScannerConfigurer 物件和通過 @MapperScan 註解掃描 Mapper 介面以外,我們還可以通過 MyBatis 提供的 `scan` 標籤來掃描 Mapper 介面 ### 示例 ```xml ``` ### spring.schemas 在 `META-INF/spring.schemas` 定義如下: ```txt http\://mybatis.org/schema/mybatis-spring-1.2.xsd=org/mybatis/spring/config/mybatis-spring.xsd http\://mybatis.org/schema/mybatis-spring.xsd=org/mybatis/spring/config/mybatis-spring.xsd ``` - xmlns 為 `http://mybatis.org/schema/mybatis-spring-1.2.xsd` 或 `http://mybatis.org/schema/mybatis-spring.xsd` - xsd 為 [mybatis-spring.xsd](https://cdn.jsdelivr.net/gh/liu844869663/spring@master/src/main/resources/org/mybatis/spring/config/mybatis-spring.xsd) ### spring.handler 在 `META-INF/spring.handlers` 定義如下: ```xml http\://mybatis.org/schema/mybatis-spring=org.mybatis.spring.config.NamespaceHandler ``` - 定義了 MyBatis 的 XML Namespace 的處理器 NamespaceHandler 物件 ### NamespaceHandler `org.mybatis.spring.config.NamespaceHandler`:繼承 NamespaceHandlerSupport 抽象類,MyBatis 的 XML Namespace 的處理器,程式碼如下: ```java public class NamespaceHandler extends NamespaceHandlerSupport { /** * {@inheritDoc} */ @Override public void init() { registerBeanDefinitionParser("scan", new MapperScannerBeanDefinitionParser()); } } ``` - `` 標籤,使用 MapperScannerBeanDefinitionParser 解析 ### MapperScannerBeanDefinitionParser `org.mybatis.spring.config.MapperScannerBeanDefinitionParser`:實現 BeanDefinitionParser 介面,`` 的解析器,程式碼如下: ```java public class MapperScannerBeanDefinitionParser extends AbstractBeanDefinitionParser { private static final String ATTRIBUTE_BASE_PACKAGE = "base-package"; private static final String ATTRIBUTE_ANNOTATION = "annotation"; private static final String ATTRIBUTE_MARKER_INTERFACE = "marker-interface"; private static final String ATTRIBUTE_NAME_GENERATOR = "name-generator"; private static final String ATTRIBUTE_TEMPLATE_REF = "template-ref"; private static final String ATTRIBUTE_FACTORY_REF = "factory-ref"; private static final String ATTRIBUTE_MAPPER_FACTORY_BEAN_CLASS = "mapper-factory-bean-class"; private static final String ATTRIBUTE_LAZY_INITIALIZATION = "lazy-initialization"; private static final String ATTRIBUTE_DEFAULT_SCOPE = "default-scope"; /** * 這個方法和 {@link MapperScannerRegistrar} 一樣的作用 * 解析 標籤中的配置資訊,設定到 MapperScannerConfigurer 的 BeanDefinition 物件中 * {@inheritDoc} * * @since 2.0.2 */ @Override protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { // 建立一個 BeanDefinition 構建器,用於構建 MapperScannerConfigurer 的 BeanDefinition 物件 BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); ClassLoader classLoader = ClassUtils.getDefaultClassLoader(); // 新增是否處理屬性中的佔位符屬性 builder.addPropertyValue("processPropertyPlaceHolders", true); /* * 解析 `scan` 標籤中的配置,新增到 BeanDefinition 中 */ try { String annotationClassName = element.getAttribute(ATTRIBUTE_ANNOTATION); if (StringUtils.hasText(annotationClassName)) { @SuppressWarnings("unchecked") Class annotationClass = (Class) classLoader .loadClass(annotationClassName); builder.addPropertyValue("annotationClass", annotationClass); } String markerInterfaceClassName = element.getAttribute(ATTRIBUTE_MARKER_INTERFACE); if (StringUtils.hasText(markerInterfaceClassName)) { Class markerInterface = classLoader.loadClass(markerInterfaceClassName); builder.addPropertyValue("markerInterface", markerInterface); } String nameGeneratorClassName = element.getAttribute(ATTRIBUTE_NAME_GENERATOR); if (StringUtils.hasText(nameGeneratorClassName)) { Class nameGeneratorClass = classLoader.loadClass(nameGeneratorClassName); BeanNameGenerator nameGenerator = BeanUtils.instantiateClass(nameGeneratorClass, BeanNameGenerator.class); builder.addPropertyValue("nameGenerator", nameGenerator); } String mapperFactoryBeanClassName = element.getAttribute(ATTRIBUTE_MAPPER_FACTORY_BEAN_CLASS); if (StringUtils.hasText(mapperFactoryBeanClassName)) { @SuppressWarnings("unchecked") Class mapperFactoryBeanClass = (Class) classLoader .loadClass(mapperFactoryBeanClassName); builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass); } } catch (Exception ex) { XmlReaderContext readerContext = parserContext.getReaderContext(); readerContext.error(ex.getMessage(), readerContext.extractSource(element), ex.getCause()); } builder.addPropertyValue("sqlSessionTemplateBeanName", element.getAttribute(ATTRIBUTE_TEMPLATE_REF)); builder.addPropertyValue("sqlSessionFactoryBeanName", element.getAttribute(ATTRIBUTE_FACTORY_REF)); builder.addPropertyValue("lazyInitialization", element.getAttribute(ATTRIBUTE_LAZY_INITIALIZATION)); builder.addPropertyValue("defaultScope", element.getAttribute(ATTRIBUTE_DEFAULT_SCOPE)); builder.addPropertyValue("basePackage", element.getAttribute(ATTRIBUTE_BASE_PACKAGE)); return builder.getBeanDefinition(); } /** * {@inheritDoc} * * @since 2.0.2 */ @Override protected boolean shouldGenerateIdAsFallback() { return true; } } ``` - 程式碼的實現邏輯**MapperScannerRegistrar**一致,建立MapperScannerConfigurer對應的BeanDefinition物件,然後去解析`` 標籤中的配置資訊 ## SqlSessionTemplate `org.mybatis.spring.SqlSessionTemplate`:實現 SqlSession 和 DisposableBean 介面,SqlSession 操作模板實現類 實際上,程式碼實現和 `org.apache.ibatis.session.SqlSessionManager` 相似,承擔 SqlSessionFactory 和 SqlSession 的職責 ### 構造方法 ```java public class SqlSessionTemplate implements SqlSession, DisposableBean { /** * a factory of SqlSession */ private final SqlSessionFactory sqlSessionFactory; /** * {@link Configuration} 中預設的 Executor 執行器型別,預設 SIMPLE */ private final ExecutorType executorType; /** * SqlSessionInterceptor 代理物件 */ private final SqlSession sqlSessionProxy; /** * 異常轉換器,MyBatisExceptionTranslator 物件 */ private final PersistenceExceptionTranslator exceptionTranslator; public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType()); } public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) { this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true)); } 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()); } } ``` - `sqlSessionFactory`:用於建立 SqlSession 物件 - `executorType`:執行器型別,建立 SqlSession 物件時根據它建立對應的 Executor 執行器,預設為 - `sqlSessionProxy`:SqlSession 的動態代理物件,代理類為 `SqlSessionInterceptor` - `exceptionTranslator`:異常轉換器 在呼叫SqlSessionTemplate中的SqlSession相關方法時,內部都是直接呼叫`sqlSessionProxy`動態代理物件的方法,我們來看看是如何處理的 ### SqlSessionInterceptor SqlSessionTemplate的內部類,實現了 InvocationHandler 介面,作為`sqlSessionProxy`動態代理物件的代理類,對 SqlSession 的相關方法進行增強 程式碼如下: ```java private class SqlSessionInterceptor implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // <1> 獲取一個 SqlSession 物件 SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); try { // 執行 SqlSession 的方法 Object result = method.invoke(sqlSession, args); // 當前 SqlSession 不處於 Spring 託管的事務中 if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { // force commit even on non-dirty sessions because some databases require // a commit/rollback before calling close() // 強制提交 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 // 關閉 SqlSession 會話,釋放資源 closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); sqlSession = null; // 對異常進行轉換,差不多就是轉換成 MyBatis 的異常 Throwable translated = SqlSessionTemplate.this.exceptionTranslator .translateExceptionIfPossible((PersistenceException) unwrapped); if (translated != null) { unwrapped = translated; } } throw unwrapped; } finally { if (sqlSession != null) { closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } } } } ``` 1. 呼叫`SqlSessionUtils`的`getSqlSession`方法,獲取一個 SqlSession 物件 2. 執行 SqlSession 的方法 3. 當前 SqlSession 不處於 Spring 託管的事務中,則強制提交 4. 呼叫`SqlSessionUtils`的`closeSqlSession`方法,“關閉”SqlSession 物件,這裡的關閉不是真正的關閉 ## SqlSessionHolder `org.mybatis.spring.SqlSessionHolder`:繼承 `org.springframework.transaction.support.ResourceHolderSupport` 抽象類,SqlSession 持有器,用於儲存當前 SqlSession 物件,儲存到 `org.springframework.transaction.support.TransactionSynchronizationManager` 中,程式碼如下: ```java public final class SqlSessionHolder extends ResourceHolderSupport { /** * SqlSession 物件 */ private final SqlSession sqlSession; /** * 執行器型別 */ private final ExecutorType executorType; /** * PersistenceExceptionTranslator 物件 */ private final PersistenceExceptionTranslator exceptionTranslator; public SqlSessionHolder(SqlSession sqlSession, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sqlSession, "SqlSession must not be null"); notNull(executorType, "ExecutorType must not be null"); this.sqlSession = sqlSession; this.executorType = executorType; this.exceptionTranslator = exceptionTranslator; } } ``` - 當儲存到 TransactionSynchronizationManager 中時,使用的 KEY 為建立該 SqlSession 物件的 SqlSessionFactory 物件,後續會分析 ## SqlSessionUtils `org.mybatis.spring.SqlSessionUtils`:SqlSession 工具類,負責處理 MyBatis SqlSession 的生命週期,藉助 Spring 的 TransactionSynchronizationManager 事務管理器管理 SqlSession 物件 ### getSqlSession方法 `getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator)`方法,註釋如下: > Gets an SqlSession from Spring Transaction Manager or creates a new one if needed. Tries to get a SqlSession out of current transaction. > If there is not any, it creates a new one. Then, it synchronizes the SqlSession with the transaction if Spring TX is active and SpringManagedTransactionFactory is configured as a transaction manager. 從事務管理器(執行緒安全)中獲取一個 SqlSession 物件,如果不存在則建立一個 SqlSession,然後註冊到事務管理器中,方法如下: ```java 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 holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); // 獲取到 SqlSession 物件 SqlSession session = sessionHolder(executorType, holder); if (session != null) { return session; } LOGGER.debug(() -> "Creating a new SqlSession"); // 上面沒有獲取到,則建立一個 SqlSession session = sessionFactory.openSession(executorType); // 將上面建立的 SqlSession 封裝成 SqlSessionHolder,往 Spring 事務管理器註冊 registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session); return session; } ``` 1. 從 Spring 事務管理器中,根據 SqlSessionFactory 獲取一個 SqlSessionHolder 物件 2. 呼叫 `sessionHolder` 方法,獲取到 SqlSession 物件,方法如下 ```java private static SqlSession sessionHolder(ExecutorType executorType, SqlSessionHolder holder) { SqlSession session = null; if (holder != null && holder.isSynchronizedWithTransaction()) { // 如果執行器型別發生了變更,丟擲 TransientDataAccessResourceException 異常 if (holder.getExecutorType() != executorType) { throw new TransientDataAccessResourceException( "Cannot change the ExecutorType when there is an existing transaction"); } // 增加計數,關閉 SqlSession 時使用 holder.requested(); LOGGER.debug(() -> "Fetched SqlSession [" + holder.getSqlSession() + "] from current transaction"); // 獲得 SqlSession 物件 session = holder.getSqlSession(); } return session; } ``` 3. 如果 SqlSession 物件不為 null,則直接返回,接下來會建立一個 4. 上面沒有獲取到,則建立一個 SqlSession 物件 5. 呼叫 `registerSessionHolder` 方法,將上面建立的 SqlSession 封裝成 SqlSessionHolder,往 Spring 事務管理器註冊 6. 返回新建立的 SqlSession 物件 ### registerSessionHolder方法 `registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator, SqlSession session)`方法 註釋如下: > Register session holder if synchronization is active (i.e. a Spring TX is active). > > Note: The DataSource used by the Environment should be synchronized with the transaction either through DataSourceTxMgr or another tx synchronization. > > Further assume that if an exception is thrown, whatever started the transaction will handle closing / rolling back the Connection associated with the SqlSession. 如果事務管理器處於啟用狀態,則將 SqlSession 封裝成 SqlSessionHolder 物件註冊到其中,方法如下: ```java private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator, SqlSession session) { SqlSessionHolder holder; if (TransactionSynchronizationManager.isSynchronizationActive()) { Environment environment = sessionFactory.getConfiguration().getEnvironment(); // <1> 如果使用 Spring 事務管理器 if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) { LOGGER.debug(() -> "Registering transaction synchronization for SqlSession [" + session + "]"); // <1.1> 建立 SqlSessionHolder 物件 holder = new SqlSessionHolder(session, executorType, exceptionTranslator); // <1.2> 繫結到 TransactionSynchronizationManager 中