1 什么是 MyBatis-Spring?
在单独使用MyBatis的过程中,需要自己创建 SqlSessionFactory 和 SqlSession,然后获取到 Mapper 接口的动态代理对象,执行数据库相关操作。
在日常开发中我们所使用的框架都需要和Spring进行整合,在结合 Spring 来使用 MyBatis的时候,需要将Mybatis所产生的对象放入到Spring容器中作为 Spring Bean 注入到 Spring 容器,也允许参与到 Spring 的事务管理之中。
再日常开发中我们所使用的框架都需要和Spring进行整合。
而整合的核心思想就是把其他框架所产生的对象放到Spring容器中,让其成为Bean。
MyBatis-Spring 就是做这件事的,把Mybatis所产生的对象放入到Spring容器中,让他们Spring托管的Bean,
官网解释
MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession 并注入到 bean 中,以及将 Mybatis 的异常转换为 Spring 的 DataAccessException。 最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring。
- MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。
- Spring整合MyBatis, 主要目的就是把MyBatis核心配置文件中的内容,交给Spring来处理; 再简单点来说, 将MyBatis的SqlSessionFactory和Mapper的创建交给Spring处理;
单独使用Mybatis的Demo
public static void main(String[] args) throws Exception {
Reader reader=Resources.getResourceAsReader("resource/configuration.xml");
SqlSessionFactory sessionFactory=new SqlSessionFactoryBuilder().build(reader);
SqlSession session=sessionFactory.openSession();
PersonDao personDao=session.getMapper(PersonDao.class);
Person person=new Person("11","evan");
personDao.save(person);
session.commit();
session.close();
}
官网
在Spring中使用 MyBatis,需要在 Spring 应用上下文中创建两个对象
- SqlSessionFactory
- 数据映射器类。
- Spring Mybatis 整合Demo,水这篇文章主要是为了总结FactoryBean的使用。
- MapperFactoryBean/MapperScannerConfigurer的作用是将接口(xxxMapper.java)加入到 Spring 中
在spring Mybatis 整合Demo 的例子中spring-dao.xml文件中创建了SqlSessionFactoryBean和MapperScannerConfigurer对象。分别对应着SqlSessionFactory和数据映射器类。
- 正如开头所说,在日常开发中我们所使用的框架都需要和Spring进行整合。
- 而整合的核心思想就是把其他框架所产生的对象放到Spring容器中,让其成为Bean。
2 SqlSessionFactory的创建
2.1 SqlSessionFactory的创建方式
- 在 MyBatis 中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory
- 而在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean 来创建
在 MyBatis-Spring 中,可使用 SqlSessionFactoryBean来创建 SqlSessionFactory。
方式一:
@Configuration
public class MyBatisConfig {
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource());
return factoryBean.getObject();
}
}
方式二:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>
可以看到方式一 方式二 都是向容器中注册了一个SqlSessionFactoryBean。
那么如何将一个第三方的对象(完全由程序员控制对象创建过程)交给Spring管理?一般情况都会使用FactoryBean这种方式实现。
Spring-Mybatis插件也是基于FactoryBean实现的。
2.2 SqlSessionFactoryBean
SqlSessionFactoryBean负责构建一个SqlSessionFactory对象。
public class SqlSessionFactoryBean
implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
可以看到SqlSessionFactoryBean实现了3个接口,3个接口的作用:
- InitializingBean接口:实现了这个接口,那么当bean初始化的时候,spring就会调用该接口的实现类的afterPropertiesSet方法,去实现当spring初始化该Bean 的时候所需要的逻辑。
- FactoryBean接口:实现了该接口的类,在调用getBean的时候会返回该工厂返回的实例对象,也就是再调一次getObject方法返回工厂的实例。
- ApplicationListener接口:实现了该接口,如果注册了该监听的话,那么就可以了监听到Spring的一些事件,然后做相应的处理
2.2.1 InitializingBean
@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
2.2.1.1 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.b步生成的 Configuration 对象,那么调用XMLConfigBuilder的parse()方法进行解析,初始化 MyBatis。
- 如果配置了 XML 映射文件的路径mapperLocations,则进行遍历依次解析,通过创建XMLMapperBuilder对象,调用其parse()方法进行解析。
- 通过SqlSessionFactoryBuilder构建器创建一个DefaultSqlSessionFactory对象
DefaultSqlSessionFactory是org.apache.ibatis.session.DefaultSqlSessionFactory。是Mybatis框架的
2.2.1.2 总结
其核心作用就是读取 MyBatis 配置,初始化 Configuration 全局配置对象,并创建 SqlSessionFactory 对象,对应的核心方法是 buildSqlSessionFactory() 方法。
2.2.2 getObject方法
getObject()方法,在 Spring 容器中,注入当前 Bean 时调用该方法,也就是返回 DefaultSqlSessionFactory 对象,方法如下:
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
// 如果为空则初始化 调用InitializingBean接口中的afterPropertiesSet返回一个sqlSessionFactory
afterPropertiesSet();
}
// 返回 DefaultSqlSessionFactory 对象
return this.sqlSessionFactory;
}
3 数据映射器类的创建
3.1 MapperFactoryBean
可以通过 MapperFactoryBean 直接将 Mapper 接口注入 Service 层的 Bean 中,由 Mapper 接口完成 DAO 层的功能。
下面是一段 MapperFactoryBean 的配置示例:
<!--通过 MapperFactoryBean 将接口加入到 Spring 中-->
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<!-- 配置SqlSessionFactory,用于创建底层的SqlSessionTemplate -->
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
<!-- 配置Mapper接口 -->
<property name="mapperInterface" value=" com.evan.mapper.UserMapper"/>
</bean>
3.1.1 源码分析
Spring在实例化Mapper实例时,实际上首先会实例化MapperFactoryBean,然后再调用它的getObject方法。我们知道在Java里面接口是肯定不能被实例化的,那这个被实例化的对象只能是一个代理对象,所以getObject方法是用来创建Mapper接口的代理对象。
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
//...
@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();
}
}
}
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
属性
- mapperInterface:对应的Mapper接口
- addToConfig:是否添加到 Configuration 中,默认为 true
实现了FactoryBean接口,重写了接口中的三个方法。在getObject()方法中,通过调用getSqlSession().getMapper(this.mapperInterface)返回了一个对象。这一行代码最终会调用到MapperProxyFactory的newInstance()方法,为每一个Mapper创建一个代理对象。
checkDaoConfig
重写org.springframework.dao.support.DaoSupport#checkDaoConfig()方法,此处使用了模版模式。
checkDaoConfig方法逻辑
- 校验 sqlSessionTemplate 非空
- 校验 mapperInterface 非空
- 如果该 Mapper 接口没有被解析至 Configuration,则对其进行解析
因为MapperFactoryBean继承了DaoSupport抽象类,实现了 InitializingBean 接口,在 afterPropertiesSet() 方法中会调用checkDaoConfig()方法
getObject方法(⭐️⭐️⭐️)——获取Mapper的实例
完成 Mapper 接口的注册之后,我们就可以通过 MapperFactoryBean.getObject() 方法获取相应 Mapper 接口的代理对象。
@Override
public T getObject() throws Exception {
// getSqlSession() 方法返回 SqlSessionTemplate 对象,
// 然后调用getObject()方法获取相应Mapper接口的代理类
return getSqlSession().getMapper(this.mapperInterface);
}
getSqlSession()方法在SqlSessionDaoSupport中定义,返回的是SqlSessionTemplate对象。可以先暂时理解为就是返回一个 org.apache.ibatis.session.defaults.DefaultSqlSession,获取mapperInterfaceMapper接口对应的动态代理对象
这也就是为什么在Spring中注入Mapper接口Bean时,我们可以直接调用它的方法
getSqlSession()
- org.mybatis.spring.mapper.MapperFactoryBean:继承 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");
}
}
- checkDaoConfig()方法,校验 SqlSessionTemplate 非空
- setSqlSessionFactory(SqlSessionFactory sqlSessionFactory)方法,将 SqlSessionFactory 构建成我们需要的 SqlSessionTemplate 对象.
这也就是为什么在Spring中注入Mapper接口Bean时,我们可以直接调用它的方法
getMapper()
getObject()方法中是从SqlSession.getMapper()方法中获取到Mapper的代理类。SqlSession.getMapper()是从configuration.getMapper()中获取使用Mapper实例化的。
// org.mybatis.spring.mapper.MapperFactoryBean#getObject
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
// org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
// org.apache.ibatis.session.Configuration#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
//org.apache.ibatis.binding.MapperRegistry#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 此处获取的是Mybatis中的MapperProxyFactory
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 生成代理对象并返回
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
//org.apache.ibatis.binding.MapperProxyFactory
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethodInvoker> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
3.1.2 总结
在 MapperFactoryBean 这个 Bean 初始化的时候,会加载 mapperInterface 配置项指定的 Mapper 接口,并调用 Configuration.addMapper() 方法将 Mapper 接口注册到 MapperRegistry,在注册过程中同时会解析对应的 Mapper.xml 配置文件。
MapperFactoryBean继承SqlSessionDaoSupport类 SqlSessionDaoSupport类实现了 Spring DaoSupport 接口,核心功能是辅助我们手写 DAO 层的代码。SqlSessionDaoSupport 内部持有一个 SqlSessionTemplate 对象(sqlSession字段),并提供了getSqlSession() 方法供子类获取该 SqlSessionTemplate 对象,所以我们在手写 DAO 层代码的时候,可以通过继承 SqlSessionDaoSupport 这个抽象类的方式,拿到 SqlSessionTemplate 对象,实现访问数据库的相关操作。
3.2 SqlSessionTemplate
在 MyBatis 中,你可以使用 SqlSessionFactory 来创建 SqlSession。 一旦你获得一个 session 之后,你可以使用它来执行映射了的语句,提交或回滚连接,最后,当不再需要它的时候,你可以关闭 session。
使用 MyBatis-Spring 之后,SqlSession 接口的实现不再直接使用 MyBatis 提供的 DefaultSqlSession 默认实现,而是使用 SqlSessionTemplate。
SqlSessionTemplate是线程安全的,生命周期由spring管理的,同spring事务一起协作来保证真正执行的SqlSession是在spring的事务中的一个SqlSession的实现类。
SqlSessionTemplate 内部持有一个 SqlSession 的代理对象(sqlSessionProxy 字段),这个代理对象是通过 JDK 动态代理方式生成的;使用的 InvocationHandler 接口是 SqlSessionInterceptor,其 invoke() 方法会拦截 SqlSession 的全部方法,并检测当前事务是否由 Spring 管理。SqlSessionTemplate真正执行增删改查都是调用代理类的接口。
org.mybatis.spring.SqlSessionTemplate:实现 SqlSession 和 DisposableBean 接口
3.2.1 构造方法
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;
// 利用JDK动态代理工具类生成了一个SqlSession的代理类
// 创建一个 SqlSession 的动态代理对象,代理类为 SqlSessionInterceptor
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class }, new SqlSessionInterceptor());
}
}
- sqlSessionFactory:用于创建 SqlSession 对象
- executorType:执行器类型,创建 SqlSession 对象时根据它创建对应的 Executor 执行器,默认为 SIMPLE
- sqlSessionProxy:SqlSession 的动态代理对象,代理类为 SqlSessionInterceptor
- exceptionTranslator:异常转换器
在调用SqlSessionTemplate中的SqlSession相关方法时,内部都是直接调用sqlSessionProxy动态代理对象的方法。
3.2.2 SqlSessionTemplate 如何初始化的
Spring获取Mapper对象是在MapperFactoryBean对象调用它的私有属性SqlSession对象的getMapper()方法来获取已经注册到MapperRegister中的对象。那我们就从MapperFactoryBean这个类出发,看看它的私有属性SqlSession是怎么初始化的。最终我们在MapperFactoryBean的父类SqlSessionDaoSupport中看到了它。
protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
3.2.3 SqlSessionTemplate 核心逻辑
进到SqlSessionTemplate的构造方法中,可以看到sqlSessionProxy属性是通过内部类SqlSessionInterceptor 完成代理对象的生成的
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;
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class }, new SqlSessionInterceptor());
}
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取真正执行任务的SqlSession,其实就是Mybatis自身的DefaultSqlSession
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
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator
.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = SqlSessionUtils.getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
// 调用SqlSession对象的相应方法
Object result = method.invoke(sqlSession, args);
// 检测事务是否由Spring进行管理,并据此决定是否提交事务
if (!isSqlSessionTransactional(sqlSession,
SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
}
return result; // 返回操作结果
}
可以看到,真正执行命令的是通过getSqlSession()方法拿到的SqlSession实现类。
//org.mybatis.spring.SqlSessionUtils
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;
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Creating a new SqlSession");
}
// 如无SqlSession,则实例化一个SqlSession,并新建一个事务
session = sessionFactory.openSession(executorType);
// 将上面创建的 SqlSession 封装成 SqlSessionHolder,往 Spring 事务管理器注册
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
这里使用的SqlSessionUtils.getSqlSession() 方法会尝试从 Spring 事务管理器中获取 SqlSession对象并返回,如果获取失败,则新建一个 SqlSession 对象并交由 Spring 事务管理器管理,同时将这个 SqlSession 返回。
3.2.4 总结
总结一下,SqlSessionTemplate主要是帮助Spring管理Mybatis的事务,通过代理模式屏蔽了SqlSession的实现细节,专注于管理SqlSession的事务。可以结合aop的方式,帮助用户自动去提交,回滚事务。
3.3 MapperScannerConfigurer
虽然通过 MapperFactoryBean 可以不写一行 Java 代码就能实现 DAO 层逻辑,但还是需要在 Spring 的配置文件中为每个 Mapper 接口配置相应的 MapperFactoryBean,这依然是有一定工作量的。如果连配置信息都不想写,那我们就可以使用 MapperScannerConfigurer 扫描指定包下的全部 Mapper 接口。
<!-- 自动扫描所有的Mapper接口与文件 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="org.evan.mapper"></property>
</bean>
3.3.1 MapperScannerConfigurer的类图
MapperScannerConfigurer用于扫描Mapper接口,借助ClassPathMapperScanner修改Mapper接口的BeanDefinition对象,将Bean的Class对象修改为MapperFactoryBean类型,那么在Spring初始化该Bean的时候,会初始化成MapperFactoryBean类型
3.3.2 MapperScannerConfigurer的实现接口
MapperScannerConfigurer这个类中具体实现如下:
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
/**
* Mapper 接口的包路径
*/
private String basePackage;
//...
}
org.mybatis.spring.mapper.MapperScannerConfigurer:实现了BeanDefinitionRegistryPostProcessor、InitializingBean接口,ApplicationContextAware、BeanNameAware接口。那么
- BeanDefinitionRegistryPostProcessor 继承 BeanFactoryPostProcessor接口
- void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
- void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
- InitializingBean
- void afterPropertiesSet() throws Exception;
- ApplicationContextAware
- void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
- BeanNameAware
- void setBeanName(String name);
有这么多扩展点
在afterPropertiesSet()方法中会校验basePackage非空
3.3.2.1 postProcessBeanDefinitionRegistry方法
postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)方法,在 BeanDefinitionRegistry 完成后进行一些处理
MapperScannerConfigurer 实现了 BeanDefinitionRegistryPostProcessor 接口,在 Spring 容器初始化的时候会触发其 postProcessBeanDefinitionRegistry() 方法,完成扫描逻辑,其核心代码逻辑如下:
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
// 解析Spring配置文件中MapperScannerConfigurer配置的占位符
processPropertyPlaceHolders();
}
// 创建ClassPathMapperScanner 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();
// 根据配置信息决定ClassPathMapperScanner如何扫描指定的包,也就是确定扫描的过滤条件,
// 例如,有几个包需要扫描、是否关注Mapper接口的注解、是否关注Mapper接口的父类等
// 开始扫描basePackage字段中指定的包及其子包
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
- 如果需要处理属性中的占位符,则调用processPropertyPlaceHolders()方法
- 创建一个 Bean 扫描器 ClassPathMapperScanner 对象
- 设置一些 Mapper 接口扫描器的属性,例如addToConfig、sqlSessionFactoryBeanName
- 调用扫描器的registerFilters()方法,添加几个过滤器,过滤指定路径下的 Mapper 接口
- 调用其scan方法,开始扫描 basePackage路径下的 Mapper 接口。
达到的目的就是会对扫描出的BeanDefinition进行重新处理,主要是把原来的BeanClass修改成了MapperFactoryBean.class。
调用scan方法开启扫描后,Spring就会将包含Mapper注解的类扫描为BeanDefinition。注意这里的扫描能力还是调用Spring的扫描器来实现的,ClassPathMapperScanner并没有修改,只是当扫描完成后,ClassPathMapperScanner会对扫描出的BeanDefinition进行重新处理,主要是把原来的BeanClass修改成了MapperFactoryBean.class:
3.3.2.2 ClassPathMapperScanner
ClassPathMapperScanner.scan() 这个扫描方法底层会调用其 doScan() 方法完成扫描,扫描过程中首先会遍历配置中指定的所有包,并根据过滤条件得到符合条件的BeanDefinitionHolder 对象;之后对这些 BeanDefinitionHolder 中记录的 Bean 类型进行改造,改造成 MapperFactoryBean 类型,同时填充 MapperFactoryBean 初始化所需的信息。这样就可以在 Spring 容器初始化的时候,为扫描到的 Mapper 接口创建对应的 MapperFactoryBean,从而进一步降低DAO 的编写成本。
org.mybatis.spring.mapper.ClassPathMapperScanner:继承了ClassPathBeanDefinitionScanner抽象类。spring是使用ClassPathBeanDefinitionScanner来进行扫描的
ClassPathMapperScanner负责执行扫描,修改扫描到的 Mapper 接口的 BeanDefinition 对象,将其 Bean Class 修改为 MapperFactoryBean,从而在 Spring 初始化该 Bean 的时候,会初始化成 MapperFactoryBean 类型,实现创建 Mapper 动态代理对象
构造方法
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
/**
* 是否要将 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;
}
- addToConfig:是否要将接口添加到 Configuration 全局配置对象中
- sqlSessionFactoryBeanName:SqlSessionFactory的Bean Name
上面这几个属性在 MapperScannerConfigurer 创建该对象的时候会进行赋值
registerFilters方法
registerFilters()方法,添加几个过滤器,用于扫描 Mapper 接口的过程中过滤出我们需要的 Mapper 接口,方法如下:
//org.mybatis.spring.mapper.ClassPathMapperScanner#registerFilters
public void registerFilters() {
// 标记是否接受所有的 Mapper 接口
boolean acceptAllInterfaces = true;
// 如果配置了注解,则扫描有该注解的 Mapper 接口
if (this.annotationClass != null) {
addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
acceptAllInterfaces = false;
}
// 如果配置了某个接口,则也需要扫描该接口
if (this.markerInterface != null) {
addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
@Override
protected boolean matchClassName(String className) {
return false;
}
});
acceptAllInterfaces = false;
}
if (acceptAllInterfaces) {
// 如果上面两个都没有配置,则接受所有的 Mapper 接口
addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
}
// 排除 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 集合,进行一些后置处理,方法如下:
//org.mybatis.spring.mapper.ClassPathMapperScanner#doScan
@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 方法,进行一些后置处理
看上面代码,就是通过调用registerFilters方法来添加includeFilter(实际类型是:TypeFilter),这个就是Spring提供的扩展点,用户指定需要被扫描的类,这里使用的是MappScan注解中annotationClass属性配置的注解类型,我们这里配置了Mapper,所以调用scan方法开启扫描后,Spring就会将包含Mapper注解的类扫描为BeanDefinition。注意这里的扫描能力还是调用Spring的扫描器来实现的,ClassPathMapperScanner并没有修改,只是当扫描完成后,ClassPathMapperScanner会对扫描出的BeanDefinition进行重新处理,主要是把原来的BeanClass修改成了MapperFactoryBean:
(⭐️⭐️⭐️)
ClassPathMapperScanner会对扫描出的BeanDefinition进行重新处理,主要是把原来的BeanClass修改成了MapperFactoryBean.class:
processBeanDefinitions方法(⭐️⭐️⭐️)
processBeanDefinitions(Set beanDefinitions)方法,对指定包路径下符合条件的BeanDefinition 对象进行一些处理,修改其 Bean Class 为 MapperFactoryBean 类型,方法如下:
// org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions
private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;
// ...
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属性
总结:Spring扫描到有Mapper注解的类,生成BeanDefinition,并且将类BeanDefinition的BeanClass的值修改为MapperFactoryBean,也就是说它的类型不再是用户自己编写的Mapper接口了,而是一个FactoryBean。
//org.mybatis.spring.mapper.MapperFactoryBean
@Override
public T getObject() throws Exception {
// getSqlSession() 方法返回 SqlSessionTemplate 对象
return getSqlSession().getMapper(this.mapperInterface);
}
总结
ClassPathMapperScanner.scan() 这个扫描方法底层会调用其 doScan() 方法完成扫描,扫描过程中首先会遍历配置中指定的所有包,并根据过滤条件得到符合条件的BeanDefinitionHolder 对象;之后对这些 BeanDefinitionHolder 中记录的 Bean 类型进行改造,改造成 MapperFactoryBean 类型,同时填充 MapperFactoryBean 初始化所需的信息。这样就可以在 Spring 容器初始化的时候,为扫描到的 Mapper 接口创建对应的 MapperFactoryBean,从而进一步降低DAO 的编写成本。
3.4 @MapperScan注解
3.4.1 @MapperScan注解的基本使用
@Mapper注解:
作用:在接口类上添加了@Mapper,在编译之后会生成相应的接口实现类
@Mapper
public interface UserDAO {
//代码
}
如果想要每个接口都要变成实现类,那么需要在每个接口类上加上@Mapper注解,比较麻烦,解决这个问题用@MapperScan
@MapperScan
作用:org.mybatis.spring.annotation.@MapperScan 注解,指定需要扫描的包,将包中符合条件的 Mapper 接口,注册成 beanClass 为 MapperFactoryBean 的 BeanDefinition 对象,从而实现创建 Mapper 对象
@Configuration
@MapperScan(basePackages = "com.evan.mapper")
public class xxxConfig {
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
}
@Import(MapperScannerRegistrar.class)将 MapperScannerRegistrar 类导入到Spring容器中,成为Spring 的一个 Bean 对象
可以发现所有的重担都交给了MapperScannerRegistrar。
3.4.2 MapperScannerRegistrar
org.mybatis.spring.annotation.MapperScannerRegistrar:实现 ImportBeanDefinitionRegistrar、ResourceLoaderAware 接口
作为@MapperScan注解的注册器,根据注解信息注册一个MapperScannerConfigurer对象,用于扫描 Mapper 接口
- ImportBeanDefinitionRegistrar
- void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry):用于注册BeanDefinition
- ResourceLoaderAware
- void setResourceLoader(ResourceLoader resourceLoader):空实现
registerBeanDefinitions方法
该方法的作用是获取MapperScan注解的配置信息,比如basePackages、annotationClass。
- basePackages表示需要扫描的路径
- annotationClass则是指定了增加了这种注解类的类需要被Spring进行管理,比如增加了Mapper注解的类需要被Spring管理。生成MapperScannerConfigurer这个类型的beanDefinition,并且把MapperScan注解的配置信息添加到该beanDefinition的属性集合中。
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));
}
}
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 接口进行解析。
见 MapperScannerConfigurer
//org.mybatis.spring.mapper.MapperScannerConfigurer#postProcessBeanDefinitionRegistry
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
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));
}
scanner.registerFilters();
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
MapperScannerRegistrar内部类RepeatingRegistrar
RepeatingRegistrar
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
//...
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 的后缀不一样
MapperScans的使用
@Configuration
@MapperScans({@MapperScan("com.evan.mapper1"),@MapperScan("com.evan.mapper2")})
public class xxxConfig {
4 总结
spring整合mybatis原理其实就是通过两个类:SqlSessionFactoryBean和MapperFactoryBean
- 配置SqlSessionFactoryBean的 Spring Bean,设置数据源属性dataSource,还可以配置configLocation(mybatis-config.xml配置文件的路径)、mapperLocations(XML映射文件的路径)等属性,这样让 Spring 和 MyBatis 完美的整合到一起了
- 配置MapperScannerConfigurer的 Spring Bean,设置basePackage(需要扫描的Mapper接口的路径)、sqlSessionFactoryBeanName(上面定义的SqlSessionFactoryBean)等属性因为实现了 BeanDefinitionRegistryPostProcessor 接口,在这些 Mapper 接口的 BeanDefinition 对象注册完毕后,可以进行一些处理在这里会修改这些 BeanDefinition 对象为 MapperFactoryBean 类型,在初始化 Spring Bean 的过程中则创建的是 MapperFactoryBean 对象。调用的时候注入该 BeanDefinition 对象则会调用MapperFactoryBean#getObject() 方法,返回的该 Mapper 接口对应的动态代理对象。
- 这样当你注入 Mapper 接口时,实际注入的是其动态代理对象
- 在SqlSessionTemplate对象中,承担 SqlSessionFactory 和 SqlSession 的职责