在前面的一系列文档中对整个 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
对象
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接口对应的动态代理对象
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
非空
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)
方法,注释如下:
从事务管理器(线程安全)中获取一个 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)
方法
注释如下:
如果事务管理器处于激活状态,则将 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)
方法,注释如下:
如果 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 体系
注释如下:
回调的时候清理资源
构造方法
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 的方案有了一定的了解,感谢大家的阅读!!!😄😄😄