精盡MyBatis原始碼分析 - MyBatis-Spring 原始碼分析
阿新 • • 發佈:2020-11-27
> 該系列文件是本人在學習 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 extends TypeHandler> defaultEnumTypeHandler;
private Class>[] typeAliases;
private String typeAliasesPackage;
private Class> typeAliasesSuperType;
private LanguageDriver[] scriptingLanguageDrivers;
private Class extends LanguageDriver> defaultScriptingLanguageDriver;
// issue #19. No default provider.
private DatabaseIdProvider databaseIdProvider;
private Class extends VFS> 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 extends Annotation> annotationClass;
private Class> markerInterface;
private Class extends MapperFactoryBean> 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 extends Annotation> annotationClass;
private Class> markerInterface;
/**
* 將 Mapper 介面轉換成 MapperFactoryBean 物件
*/
private Class extends MapperFactoryBean> 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 extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class extends Annotation> annotationClass() default Annotation.class;
Class> markerInterface() default Class.class;
String sqlSessionTemplateRef() default "";
String sqlSessionFactoryRef() default "";
Class extends MapperFactoryBean> 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 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"));
}
/*
* 獲取到配置的 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 extends Annotation> annotationClass = (Class extends Annotation>) 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 extends MapperFactoryBean> mapperFactoryBeanClass = (Class extends MapperFactoryBean>) 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 中