該系列文件是本人在學習 Mybatis 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋(Mybatis原始碼分析 GitHub 地址、Mybatis-Spring 原始碼分析 GitHub 地址、Spring-Boot-Starter 原始碼分析 GitHub 地址)進行閱讀
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,幫助你將 MyBatis 程式碼無縫地整合到 Spring 中,那麼我們一起來看看這個子專案是如何整合到 Spring 中的
在開始讀這篇文件之前,需要對 Spring 有一定的瞭解,可以結合我的原始碼註釋(Mybatis-Spring 原始碼分析 GitHub 地址)進行閱讀,MyBatis-Spring官方文件
簡述
主要涉及到的幾個類:
-
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 介面,<mybatis:scan />
的解析器,和MapperScannerRegistrar
的實現邏輯一樣 -
org.mybatis.spring.SqlSessionTemplate
:實現 SqlSession 和 DisposableBean 介面,SqlSession 操作模板實現類,承擔 SqlSessionFactory 和 SqlSession 的職責 -
org.mybatis.spring.SqlSessionUtils
:SqlSession 工具類,負責處理 MyBatis SqlSession 的生命週期,藉助 Spring 的 TransactionSynchronizationManager 事務管理器管理 SqlSession 對像
大致邏輯如下:
- 通過配置
MapperScannerConfigurer
的 Spring Bean,它會結合ClassPathMapperScanner
掃描器,對指定包路徑下的 Mapper 介面對應 BeanDefinition 物件(Spring Bean 的前身)進行修改,將其 Bean Class 修改為MapperFactoryBean
型別,從而在 Spring 初始化該 Bean 的時候,會初始化成MapperFactoryBean
物件,實現建立 Mapper 動態代理物件 - 在
MapperFactoryBean
物件中getObject()
中,根據SqlSessionTemplate
物件為該 Mapper 介面建立一個動態代理物件,也就是說在我們注入該 Mapper 介面時,實際注入的是 Mapper 介面對應的動態代理物件 SqlSessionTemplate
物件中,承擔 SqlSessionFactory 和 SqlSession 的職責,結合 Spring 的事務體系進行處理
配置示例
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="${url}" />
<property name="driverClassName" value="${driver}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
</bean>
<!-- spring和MyBatis完美整合,不需要mybatis的配置對映檔案 -->
<bean id="mySqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- bean的名稱為sqlSessionFactory會出現錯誤 -->
<property name="dataSource" ref="dataSource" />
<!-- 引入配置檔案 -->
<property name="configLocation" value="classpath:mybatis-config.xml" />
<!-- 自動掃描mapping.xml檔案 -->
<property name="mapperLocations" value="classpath:com/fullmoon/study/mapping/*.xml" />
</bean>
<!-- DAO介面所在包名,Spring會自動查詢其下的類 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.fullmoon.study.dao" />
<property name="sqlSessionFactoryBeanName" value="mySqlSessionFactory" />
</bean>
- 上面會建立
DruidDataSource
資料來源,SqlSessionFactoryBean
和MapperScannerConfigurer
物件
SqlSessionFactoryBean
org.mybatis.spring.SqlSessionFactoryBean
:實現 FactoryBean、InitializingBean、ApplicationListener 介面,負責構建一個 SqlSessionFactory 物件
關於Spring的FactoryBean
機制,不熟悉的先去了解一下,大致就是Spring在注入該型別的Bean時,呼叫的是它的getObject()
方法
構造方法
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
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 時,會呼叫該方法,方法如下:
@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();
}
- 校驗 dataSource 資料來源不能為空,所以配置該Bean時,必須配置一個資料來源
- 校驗 sqlSessionFactoryBuilder 構建器不能為空,上面預設 new 一個物件
- configuration 和 configLocation 有且只有一個不為空
- 呼叫
buildSqlSessionFactory()
方法,初始化SqlSessionFactory
buildSqlSessionFactory方法
buildSqlSessionFactory()
方法,根據配置資訊構建一個SqlSessionFactory
例項,方法如下:
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 物件
-
初始化
Configuration
全域性配置物件- 如果已存在 Configuration 物件,則直接使用該物件
- 否則,如果配置了 mybatis-config.xml 配置檔案,則建立一個 XMLConfigBuilder 物件,待解析
- 否則,建立一個 Configuration 物件
-
往 Configuration 物件中設定相關配置屬性
-
如果是
1.2
步生成的 Configuration 物件,那麼呼叫XMLConfigBuilder
的parse()
方法進行解析,初始化 MyBatis,在《MyBatis 初始化(一)之載入mybatis-config.xml》中分析過 -
如果配置了 XML 對映檔案的路徑
mapperLocations
,則進行遍歷依次解析,通過建立XMLMapperBuilder
物件,呼叫其parse()
方法進行解析,在《MyBatis 初始化(二)之載入Mapper介面與對映檔案》的XMLMapperBuilder小節中分析過 -
通過
SqlSessionFactoryBuilder
構建器建立一個DefaultSqlSessionFactory
物件
在 MyBatis-Spring 專案中,除了通過 mybatis-config.xml 配置檔案配置 Mapper 介面的方式以外,還提供了幾種配置方法,這裡的
SqlSessionFactoryBean.mapperLocations
也算一種這樣在 Spring 中,Mapper 介面和對應的 XML 對映檔名稱可以不一致,檔案中裡面配置
namepace
正確就可以了
getObject方法
getObject()
方法,在 Spring 容器中,注入當前 Bean 時呼叫該方法,也就是返回 DefaultSqlSessionFactory 物件,方法如下:
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
// 如果為空則初始化 sqlSessionFactory
afterPropertiesSet();
}
// 返回 DefaultSqlSessionFactory 物件
return this.sqlSessionFactory;
}
onApplicationEvent方法
onApplicationEvent(ApplicationEvent event)
方法,監聽 ContextRefreshedEvent 事件,如果還存在未初始化完成的 MapperStatement 們,則再進行解析,方法如下:
@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 物件,用於返回對應的動態代理物件
構造方法
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
/**
* Mapper 介面
*/
private Class<T> mapperInterface;
/**
* 是否新增到 {@link Configuration} 中,預設為 true
*/
private boolean addToConfig = true;
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
}
-
mapperInterface
:對應的Mapper介面 -
addToConfig
:是否新增到 Configuration 中,預設為 true
getObject方法
getObject()
方法,在 Spring 容器中,注入當前 Bean 時呼叫該方法,也就是返回 Mapper 介面對應的動態代理物件,方法如下:
@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 中
@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();
}
}
}
- 校驗
sqlSessionTemplate
非空 - 校驗
mapperInterface
非空 - 如果該 Mapper 介面沒有被解析至 Configuration,則對其進行解析
因為繼承了DaoSupport
抽象類,實現了 InitializingBean 介面,在 afterPropertiesSet() 方法中會呼叫checkDaoConfig()
方法
SqlSessionDaoSupport
org.mybatis.spring.support.SqlSessionDaoSupport
抽象類,繼承了 DaoSupport 抽象類,用於構建一個 SqlSessionTemplate 物件,程式碼如下:
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型別
構造方法
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 介面,方法如下:
@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));
}
- 如果需要處理屬性中的佔位符,則呼叫
processPropertyPlaceHolders()
方法 - 建立一個 Bean 掃描器
ClassPathMapperScanner
物件 - 設定一些 Mapper 介面掃描器的屬性,例如
addToConfig
、sqlSessionFactoryBeanName
- 呼叫掃描器的
registerFilters()
方法,新增幾個過濾器,過濾指定路徑下的 Mapper 介面 - 呼叫其
scan
方法,開始掃描basePackage
路徑下的 Mapper 介面
ClassPathMapperScanner
org.mybatis.spring.mapper.ClassPathMapperScanner
:繼承了ClassPathBeanDefinitionScanner抽象類
負責執行掃描,修改掃描到的 Mapper 介面的 BeanDefinition 物件(Spring Bean的前身),將其 Bean Class 修改為 MapperFactoryBean,從而在 Spring 初始化該 Bean 的時候,會初始化成 MapperFactoryBean
型別,實現建立 Mapper 動態代理物件
構造方法
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 介面,方法如下:
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");
});
}
- 標記是否接受所有的介面
- 如果配置了註解,則新增一個過濾器,需要有該註解的介面
- 如果配置了某個介面,則新增一個過濾器,必須是該介面
- 如果沒有第
2
、3
步,則新增一個過濾器,接收所有的介面 - 新增過濾器,排除 package-info.java 檔案
doScan方法
doScan(String... basePackages)
方法,掃描指定包路徑,根據上面的過濾器,獲取路徑下對應的 BeanDefinition 集合,進行一些後置處理,方法如下:
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 掃描指定包路徑,根據上面的過濾器,獲取到路徑下 Class 物件的 BeanDefinition 物件
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
+ "' package. Please check your configuration.");
} else {
// 後置處理這些 BeanDefinition
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
- 掃描指定包路徑,根據上面的過濾器,獲取到路徑下符合條件的 Resource 資源,並生成對應的
BeanDefinition
物件註冊到 BeanDefinitionRegistry 登錄檔中,並返回 - 如果不為空,則呼叫
processBeanDefinitions
方法,進行一些後置處理
processBeanDefinitions方法
processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions)
方法,對指定包路徑下符合條件的BeanDefinition
物件進行一些處理,修改其 Bean Class 為 MapperFactoryBean
型別,方法如下:
private void processBeanDefinitions(Set<BeanDefinitionHolder> 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());
}
}
}
-
獲取 BeanDefinition 登錄檔,然後開始遍歷
-
獲取對應的 Bean 的 Class 名稱,也就是 Mapper 介面的 Class 物件
-
往構造方法的引數列表中新增一個引數,為當前 Mapper 介面的名稱,因為 MapperFactoryBean 的構造方法的入參就是 Mapper 介面
-
修改該 Mapper 介面的 Class 物件 為
MapperFactoryBean
,根據第3
步則會為該 Mapper 介面建立一個對應的MapperFactoryBean
物件了 -
新增
addToConfig
屬性,Mapper 是否新增到 Configuration 中 -
開始新增
sqlSessionFactory
或者sqlSessionTemplate
屬性-
如果設定了 sqlSessionFactoryBeanName,則新增 sqlSessionFactory 屬性,實際上配置的是 SqlSessionFactoryBean 物件,
否則,如果配置了 sqlSessionFactory 物件,則新增 sqlSessionFactory 屬性
在
SqlSessionDaoSupport
的setSqlSessionFactory(SqlSessionFactory sqlSessionFactory)
方法中你會發現建立的就是SqlSessionTemplate
物件 -
如果配置了 sqlSessionTemplateBeanName,則新增 sqlSessionTemplate 屬性
否則,如果配置了 sqlSessionTemplate 物件,則新增 sqlSessionTemplate 屬性
-
上面沒有找到對應的 SqlSessionFactory,則設定通過型別注入
-
該方法的處理邏輯大致如上描述,主要做了一下幾個事:
-
將 Mapper 介面的 BeanDefinition 物件的 beanClass 屬性修改成了
MapperFactoryBean
的 Class 物件 -
新增了一個入參為 Mapper 介面,這樣初始化的 Spring Bean 就是該 Mapper 介面對應的
MapperFactoryBean
物件了 -
新增
MapperFactoryBean
物件的sqlSessionTemplate
屬性
@MapperScan註解
org.mybatis.spring.annotation.@MapperScan
註解,指定需要掃描的包,將包中符合條件的 Mapper 介面,註冊成 beanClass
為 MapperFactoryBean 的 BeanDefinition 物件,從而實現建立 Mapper 物件
我們程式碼如下:
@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)
方法
@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));
}
}
- 獲得 @MapperScan 註解資訊
- 呼叫
generateBaseBeanName
方法,為MapperScannerConfigurer
生成一個beanName:org.springframework.core.type.AnnotationMetadata#MapperScannerRegistrar#0
- 呼叫
registerBeanDefinitions
過載方法,註冊一個型別為MapperScannerConfigurer
的 BeanDefinition 物件
registerBeanDefinitions過載方法
registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName)
方法
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<String> basePackages = new ArrayList<>();
basePackages.addAll(
Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
.collect(Collectors.toList()));
basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName)
.collect(Collectors.toList()));
/*
* 如果沒有 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());
}
- 建立一個 BeanDefinition 構建器,用於構建
MapperScannerConfigurer
的 BeanDefinition 物件 - 新增是否處理屬性中的佔位符屬性
processPropertyPlaceHolders
- 依次新增
@MapperScan
註解中的配置屬性,例如:sqlSessionFactoryBeanName
和basePackages
- 往 BeanDefinitionRegistry 登錄檔註冊
MapperScannerConfigurer
型別的 BeanDefinition 物件
這樣在 Spring 容器初始化的過程中,會建立一個 MapperScannerConfigurer 物件,然後回到MapperScannerConfigurer的postProcessBeanDefinitionRegistry方法中,對包路徑下的 Mapper 介面進行解析,前面已經分析過了
RepeatingRegistrar內部類
MapperScannerRegistrar的內部類,程式碼如下:
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 的字尾不一樣
自定義 <mybatis:scan /> 標籤
除了配置 MapperScannerConfigurer 物件和通過 @MapperScan 註解掃描 Mapper 介面以外,我們還可以通過 MyBatis 提供的 scan
標籤來掃描 Mapper 介面
示例
<mybatis:scan base-package="org.mybatis.spring.sample.mapper" />
spring.schemas
在 META-INF/spring.schemas
定義如下:
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
spring.handler
在 META-INF/spring.handlers
定義如下:
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 的處理器,程式碼如下:
public class NamespaceHandler extends NamespaceHandlerSupport {
/**
* {@inheritDoc}
*/
@Override
public void init() {
registerBeanDefinitionParser("scan", new MapperScannerBeanDefinitionParser());
}
}
<mybatis:scan />
標籤,使用 MapperScannerBeanDefinitionParser 解析
MapperScannerBeanDefinitionParser
org.mybatis.spring.config.MapperScannerBeanDefinitionParser
:實現 BeanDefinitionParser 介面,<mybatis:scan />
的解析器,程式碼如下:
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} 一樣的作用
* 解析 <mybatis:scan /> 標籤中的配置資訊,設定到 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物件,然後去解析
<mybatis:scan />
標籤中的配置資訊
SqlSessionTemplate
org.mybatis.spring.SqlSessionTemplate
:實現 SqlSession 和 DisposableBean 介面,SqlSession 操作模板實現類
實際上,程式碼實現和 org.apache.ibatis.session.SqlSessionManager
相似,承擔 SqlSessionFactory 和 SqlSession 的職責
構造方法
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 的相關方法進行增強
程式碼如下:
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);
}
}
}
}
- 呼叫
SqlSessionUtils
的getSqlSession
方法,獲取一個 SqlSession 物件 - 執行 SqlSession 的方法
- 當前 SqlSession 不處於 Spring 託管的事務中,則強制提交
- 呼叫
SqlSessionUtils
的closeSqlSession
方法,“關閉”SqlSession 物件,這裡的關閉不是真正的關閉
SqlSessionHolder
org.mybatis.spring.SqlSessionHolder
:繼承 org.springframework.transaction.support.ResourceHolderSupport
抽象類,SqlSession 持有器,用於儲存當前 SqlSession 物件,儲存到 org.springframework.transaction.support.TransactionSynchronizationManager
中,程式碼如下:
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,然後註冊到事務管理器中,方法如下:
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;
}
-
從 Spring 事務管理器中,根據 SqlSessionFactory 獲取一個 SqlSessionHolder 物件
-
呼叫
sessionHolder
方法,獲取到 SqlSession 物件,方法如下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; }
-
如果 SqlSession 物件不為 null,則直接返回,接下來會建立一個
-
上面沒有獲取到,則建立一個 SqlSession 物件
-
呼叫
registerSessionHolder
方法,將上面建立的 SqlSession 封裝成 SqlSessionHolder,往 Spring 事務管理器註冊 -
返回新建立的 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 物件註冊到其中,方法如下:
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 中
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
// <1.3> 建立 SqlSessionSynchronization 到 TransactionSynchronizationManager 中
TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
// <1.4> 設定同步
holder.setSynchronizedWithTransaction(true);
// <1.5> 增加計數
holder.requested();
} else {
// <2> 如果非 Spring 事務管理器,丟擲 TransientDataAccessResourceException 異常
if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
LOGGER.debug(() -> "SqlSession [" + session
+ "] was not registered for synchronization because DataSource is not transactional");
} else {
throw new TransientDataAccessResourceException(
"SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
}
}
} else {
LOGGER.debug(() -> "SqlSession [" + session
+ "] was not registered for synchronization because synchronization is not active");
}
}
- 如果使用 Spring 事務管理器,才會進行註冊
- 建立 SqlSessionHolder 物件
holder
- 繫結到 TransactionSynchronizationManager 中,key 為 SqlSessionFactory 物件
- 建立
SqlSessionSynchronization
物件(事務同步器)到 TransactionSynchronizationManager 中 - 設定
holder
的synchronizedWithTransaction
屬性為ture,和事務繫結了 - 增加
holder
的referenceCount
引用數量
closeSqlSession方法
closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory)
方法,註釋如下:
Checks if SqlSession passed as an argument is managed by Spring TransactionSynchronizationManager
If it is not, it closes it, otherwise it just updates the reference counter and lets Spring call the close callback when the managed transaction ends
如果 SqlSessionFactory 是由 Spring 的事務管理器管理,並且和入參中的 session
相同,那麼只進行釋放,也就是將 referenceCount
引用數量減一,否則就直接關閉了
方法如下:
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
notNull(session, NO_SQL_SESSION_SPECIFIED);
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
// <1> 從 TransactionSynchronizationManager 中,獲得 SqlSessionHolder 物件
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
// <2.1> 如果相等,說明在 Spring 託管的事務中,則釋放 holder 計數
if ((holder != null) && (holder.getSqlSession() == session)) {
LOGGER.debug(() -> "Releasing transactional SqlSession [" + session + "]");
holder.released();
} else {
// <2.2> 如果不相等,說明不在 Spring 託管的事務中,直接關閉 SqlSession 物件
LOGGER.debug(() -> "Closing non transactional SqlSession [" + session + "]");
session.close();
}
}
- 從事務管理器中,根據 SqlSessionFactory 獲得 SqlSessionHolder 物件
- 如果相等,說明在 Spring 託管的事務中,則釋放 holder 計數
- 否則,不在 Spring 託管的事務中,直接關閉 SqlSession 物件
isSqlSessionTransactional方法
isSqlSessionTransactional(SqlSession session, SqlSessionFactory sessionFactory)
方法,判斷 SqlSession 物件是否被 Sping 的事務管理器管理,程式碼如下:
public static boolean isSqlSessionTransactional(SqlSession session, SqlSessionFactory sessionFactory) {
notNull(session, NO_SQL_SESSION_SPECIFIED);
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
// 從 TransactionSynchronizationManager 中,獲得 SqlSessionHolder 物件
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
// 如果相等,說明在 Spring 託管的事務中
return (holder != null) && (holder.getSqlSession() == session);
}
SqlSessionSynchronization
org.mybatis.spring.SqlSessionUtils
的內部類,繼承了 TransactionSynchronizationAdapter 抽象類,SqlSession 的事務同步器,基於 Spring Transaction 體系
註釋如下:
Callback for cleaning up resources.
It cleans TransactionSynchronizationManager and also commits and closes the SqlSession.
It assumes that Connection life cycle will be managed by DataSourceTransactionManager or JtaTransactionManager
回撥的時候清理資源
構造方法
private static final class SqlSessionSynchronization extends TransactionSynchronizationAdapter {
private final SqlSessionHolder holder;
private final SqlSessionFactory sessionFactory;
/**
* 是否開啟
*/
private boolean holderActive = true;
public SqlSessionSynchronization(SqlSessionHolder holder, SqlSessionFactory sessionFactory) {
notNull(holder, "Parameter 'holder' must be not null");
notNull(sessionFactory, "Parameter 'sessionFactory' must be not null");
this.holder = holder;
this.sessionFactory = sessionFactory;
}
}
getOrder方法
@Override
public int getOrder() {
// order right before any Connection synchronization
return DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 1;
}
suspend方法
當事務掛起時,取消當前執行緒的繫結的 SqlSessionHolder 物件,方法如下:
@Override
public void suspend() {
if (this.holderActive) {
LOGGER.debug(() -> "Transaction synchronization suspending SqlSession [" + this.holder.getSqlSession() + "]");
TransactionSynchronizationManager.unbindResource(this.sessionFactory);
}
}
resume方法
當事務恢復時,重新繫結當前執行緒的 SqlSessionHolder 物件,方法如下:
@Override
public void resume() {
if (this.holderActive) {
LOGGER.debug(() -> "Transaction synchronization resuming SqlSession [" + this.holder.getSqlSession() + "]");
TransactionSynchronizationManager.bindResource(this.sessionFactory, this.holder);
}
}
beforeCommit方法
在事務提交之前,呼叫 SqlSession#commit() 方法之前,提交事務。雖然說,Spring 自身也會呼叫 Connection#commit() 方法,進行事務的提交。但是,SqlSession#commit() 方法中,不僅僅有事務的提交,還有提交批量操作,重新整理本地快取等等,方法如下:
@Override
public void beforeCommit(boolean readOnly) {
// Connection commit or rollback will be handled by ConnectionSynchronization or DataSourceTransactionManager.
// But, do cleanup the SqlSession / Executor, including flushing BATCH statements so they are actually executed.
// SpringManagedTransaction will no-op the commit over the jdbc connection
// TODO This updates 2nd level caches but the tx may be rolledback later on!
if (TransactionSynchronizationManager.isActualTransactionActive()) {
try {
LOGGER.debug(() -> "Transaction synchronization committing SqlSession [" + this.holder.getSqlSession() + "]");
// 提交事務
this.holder.getSqlSession().commit();
} catch (PersistenceException p) {
// 如果發生異常,則進行轉換,並丟擲異常
if (this.holder.getPersistenceExceptionTranslator() != null) {
DataAccessException translated = this.holder.getPersistenceExceptionTranslator()
.translateExceptionIfPossible(p);
if (translated != null) {
throw translated;
}
}
throw p;
}
}
}
beforeCompletion方法
提交事務完成之前,關閉 SqlSession 物件,在 beforeCommit 之後呼叫,方法如下:
@Override
public void beforeCompletion() {
// Issue #18 Close SqlSession and deregister it now
// because afterCompletion may be called from a different thread
if (!this.holder.isOpen()) {
LOGGER.debug(() -> "Transaction synchronization deregistering SqlSession [" + this.holder.getSqlSession() + "]");
// 取消當前執行緒的繫結的 SqlSessionHolder 物件
TransactionSynchronizationManager.unbindResource(sessionFactory);
// 標記無效
this.holderActive = false;
LOGGER.debug(() -> "Transaction synchronization closing SqlSession [" + this.holder.getSqlSession() + "]");
// 關閉 SqlSession 物件
this.holder.getSqlSession().close();
}
}
afterCompletion方法
在事務完成之後,關閉 SqlSession 物件,解決可能出現的跨執行緒的情況,方法如下:
@Override
public void afterCompletion(int status) {
if (this.holderActive) { // 處於有效狀態
// afterCompletion may have been called from a different thread
// so avoid failing if there is nothing in this one
LOGGER.debug(() -> "Transaction synchronization deregistering SqlSession [" + this.holder.getSqlSession() + "]");
// 取消當前執行緒的繫結的 SqlSessionHolder 物件
TransactionSynchronizationManager.unbindResourceIfPossible(sessionFactory);
// 標記無效
this.holderActive = false;
LOGGER.debug(() -> "Transaction synchronization closing SqlSession [" + this.holder.getSqlSession() + "]");
// 關閉 SqlSession 物件
this.holder.getSqlSession().close();
}
this.holder.reset();
}
總結
還有一部分內容在org.mybatis.spring.batch
包路徑下,基於 Spring Batch 框架,Spring 和 MyBatis 的批處理進行整合,感興趣的小夥伴可以去閱讀一下
我通常都是通過配置示例中方式配置MyBatis的,因為我覺得配置檔案易於維護,比較可觀,當然也通過註解(@MapperScan
)的方式進行配置,原理相同
-
首先配置
DataSource
資料來源的 Sping Bean,我們通常不會使用 MyBatis 自帶的資料來源,因為其效能不好,都是通過Druid
或者HikariCP
等第三方元件來實現 -
配置
SqlSessionFactoryBean
的 Spring Bean,設定資料來源屬性dataSource
,還可以配置configLocation
(mybatis-config.xml配置檔案的路徑)、mapperLocations
(XML對映檔案的路徑)等屬性,這樣讓 Spring 和 MyBatis 完美的整合到一起了 -
配置
MapperScannerConfigurer
的 Spring Bean,設定basePackage
(需要掃描的Mapper介面的路徑)、sqlSessionFactoryBeanName
(上面定義的SqlSessionFactoryBean)等屬性因為實現了
BeanDefinitionRegistryPostProcessor
介面,在這些 Mapper 介面的 BeanDefinition 物件(Spring Bean 的前身)註冊完畢後,可以進行一些處理在這裡會修改這些 BeanDefinition 物件為
MapperFactoryBean
型別,在初始化 Spring Bean 的過程中則建立的是 MapperFactoryBean 物件,注入該物件則會呼叫其getObject()
方法,返回的該 Mapper 介面對應的動態代理物件這樣當你注入 Mapper 介面時,實際注入的是其動態代理物件
-
在
SqlSessionTemplate
物件中,承擔 SqlSessionFactory 和 SqlSession 的職責
到這裡,相信大家對 MyBatis 整合到 Spring 的方案有了一定的瞭解,感謝大家的閱讀!!!???
參考文章:芋道原始碼《精盡 MyBatis 原始碼分析》