參考 知識星球 中 芋道源碼 星球的源碼解析,一個活躍度非常高的 Java 技術社群,感興趣的小伙伴可以加入 芋道源碼 星球,一起學習😄
該系列文檔是本人在學習 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 框架進行了分析,相信你對 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 源碼分析》