欢迎关注MyBatis源码阅读专栏,持续更新中~~
一、概述
通过前面几篇文章的学习,相信小伙伴对Mybatis的认识更加深刻了,对整体的流程应该算是比较清晰了。但是我们在项目中很少单独使用Mybatis,一般都是集成到Spring中,由Spring来帮我们完成以前很多繁琐的步骤,比如管理SqlSessionFactory、创建SqlSession,并且不需要手动调用getMapper方法去获取mapper接口,直接使用autoWired自动注入进来就好了。那么Spring到底是如何整合Mybatis的,我们有必要去了解一下。
二、Spring加载MyBatis过程
首先来回顾一下,没有集成Spring的时候,Mybatis是如何使用的:
代码语言:javascript复制public static void main(String[] args) {
//1、读取配置文件
String resource = "mybatis-config.xml";
InputStream inputStream;
SqlSession sqlSession = null;
try {
inputStream = Resources.getResourceAsStream(resource);
//2、初始化mybatis,创建SqlSessionFactory类实例
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//3、创建Session实例
sqlSession = sqlSessionFactory.openSession();
//4、获取Mapper接口
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//5、执行SQL操作
User user = userMapper.getById(1L);
System.out.println(user);
} catch (IOException e) {
e.printStackTrace();
} finally {
//6、关闭sqlSession会话
if (null != sqlSession) {
sqlSession.close();
}
}
}
大体步骤:
- 1、加载Mybatis的全局配置文件;
- 2、初始化mybatis,创建SqlSessionFactory类实例;
- 3、创建Session会话;
- 4、获取Mapper接口;
- 5、执行具体SQL;
其中加载全局配置文件、创建SqlSessionFactory、扫描mapper接口都是比较重要的,所以分析Spring加载MyBatis的过程无非也就是从这几方面入手。
我们来看看Spring要集成Mybatis需要做哪些配置:
代码语言:javascript复制<!--定义数据源-->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/user_mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!--定义sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--数据库连接池 -->
<property name="dataSource" ref="dataSource"/>
<!--配置mybatis全局配置文件: mybatis-config.xml -->
<property name="configLocation" value="classpath:/mybatis-config.xml"/>
<!--扫描entity包,使用别名,多个用;隔开 -->
<property name="typeAliasesPackage" value="entity"/>
<!--扫描sql配置文件:mapper需要的xml文件 -->
<property name="mapperLocations" value="classpath:/mapper/*.xml"/>
</bean>
<!-- 配置mapper接口路径,并注入到spring容器中 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="com.wsh.mybatis.mybatisdemo.mapper"/>
</bean>
我们看到,有两个主要的配置类:
- SqlSessionFactoryBean:Spring就是利用它来创建Mybatis的SqlSessionFactory;
- MapperScannerConfigurer:Spring使用它来扫描我们的mapper接口,并注册到IOC中;
下面分别来看看Spring是如何帮我们自动创建SqlSessionFactory的,SqlSessionFactoryBean类的继承关系图如下:
可以看到SqlSessionFactoryBean类实现了三个接口,一个是InitializingBean,另一个是FactoryBean,还有就是ApplicationListener接口。
- InitializingBean接口:如果某个bean实现了这个接口,当bean初始化的时候,spring就会调用该接口的实现类的afterPropertiesSet方法,去实现当spring初始化该Bean的时候所需要的逻辑;
- FactoryBean接口:如果某个bean实现了这个接口,在调用getBean的时候会返回该工厂返回的实例对象,也就是再调一次getObject方法返回工厂的实例;
- ApplicationListener接口:如果某个bean实现了这个接口,如果注册了该监听的话,那么就可以了监听到Spring的一些事件,然后做相应的处理;
所以我们先来看看SqlSessionFactoryBean类的afterPropertiesSet()做了什么事情?
代码语言:javascript复制public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
private static final Log LOGGER = LogFactory.getLog(SqlSessionFactoryBean.class);
/**
* 实现了InitializingBean接口的Bean,在Spring初始化的时候会回调afterPropertiesSet()方法
*/
@Override
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
//具体创建SqlSessionFactory的方法
this.sqlSessionFactory = buildSqlSessionFactory();
}
}
从源码中可以看到,通过buildSqlSessionFactory()方法创建了一个SqlSessionFactory,继续跟踪代码:
代码语言:javascript复制/**
* 构建一个SqlSessionFactory实例
*
* 默认实现使用标准的MyBatis XMLConfigBuilderAPI来构建
* 基于读取器的SqlSessionFactory实例。
* 从1.3.0开始,它可以被直接指定为Configuration实例(不需要配置文件)。
*/
//org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
//全局配置对象,后续很多解析XML之后的配置都会保存在configuration对象中.
Configuration configuration;
//XMLConfigBuilder这个类应该比较熟悉,就是Mybatis解析XML配置的核心类
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
configuration = this.configuration;
if (configuration.getVariables() == null) {
configuration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
configuration.getVariables().putAll(this.configurationProperties);
}
} else if (this.configLocation != null) {
//初始化xmlConfigBuilder,通过我们配置的configLocation全局配置的位置
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
}
configuration = new Configuration();
if (this.configurationProperties != null) {
configuration.setVariables(this.configurationProperties);
}
}
//设置对象工厂
if (this.objectFactory != null) {
configuration.setObjectFactory(this.objectFactory);
}
if (this.objectWrapperFactory != null) {
configuration.setObjectWrapperFactory(this.objectWrapperFactory);
}
if (this.vfs != null) {
configuration.setVfsImpl(this.vfs);
}
//处理别名配置
if (hasLength(this.typeAliasesPackage)) {
String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeAliasPackageArray) {
configuration.getTypeAliasRegistry().registerAliases(packageToScan,
typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Scanned package: '" packageToScan "' for aliases");
}
}
}
if (!isEmpty(this.typeAliases)) {
for (Class<?> typeAlias : this.typeAliases) {
configuration.getTypeAliasRegistry().registerAlias(typeAlias);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered type alias: '" typeAlias "'");
}
}
}
//处理插件相关
if (!isEmpty(this.plugins)) {
for (Interceptor plugin : this.plugins) {
configuration.addInterceptor(plugin);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered plugin: '" plugin "'");
}
}
}
//处理类型转换器相关逻辑
if (hasLength(this.typeHandlersPackage)) {
String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeHandlersPackageArray) {
configuration.getTypeHandlerRegistry().register(packageToScan);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Scanned package: '" packageToScan "' for type handlers");
}
}
}
if (!isEmpty(this.typeHandlers)) {
for (TypeHandler<?> typeHandler : this.typeHandlers) {
configuration.getTypeHandlerRegistry().register(typeHandler);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered type handler: '" typeHandler "'");
}
}
}
if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
try {
configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
}
//处理缓存相关
if (this.cache != null) {
configuration.addCache(this.cache);
}
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
if (LOGGER.isDebugEnabled()) {
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();
}
}
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
//设置Environment环境参数
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
if (!isEmpty(this.mapperLocations)) {
//mapper接口路径
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
//执行XML解析
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" mapperLocation "'", e);
} finally {
ErrorContext.instance().reset();
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed mapper file: '" mapperLocation "'");
}
}
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
}
}
//最后这里调用的是sqlSessionFactoryBuilder.build构建一个sqlSessionFactory,并返回
return this.sqlSessionFactoryBuilder.build(configuration);
}
从源码中我们可以看到buildSqlSessionFactory()方法主要做了几件事情:
- 1、组装全局配置对象Configuration,将很多解析XML之后的配置都会保存在configuration对象中;
- 2、使用这些配置通过SqlSessionFactoryBuilder创建SqlSessionFactory;
- 3、最后调用了sqlSessionFactoryBuilder.build();
下面我们看一下sqlSessionFactoryBuilder.build()方法,发现此方法就是Mybatis创建sqlSessionFactory的方法,在前面的文章中有详细介绍过,这里就不过多赘述了。
代码语言:javascript复制//org.apache.ibatis.session.SqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration)
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
到这里,SqlSessionFactory已经创建完成了,即SqlSessionFactoryBean的初始化完成。
下面我们看一下如何获取SqlSessionFactoryBean实例,前面说过SqlSessionFactoryBean实现了FactoryBean接口,所以当我们通过getBean获取它的实例的时候实际是调用它的getObject方法,获取到的是sqlSessionFactory。
代码语言:javascript复制/**
* 返回IOC容器的Bean
*/
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
//返回创建好的sqlSessionFactory
//sqlSessionFactory就是在afterPropertiesSet()方法执行完后赋值的
return this.sqlSessionFactory;
}
/**
* 返回IOC容器Bean的类型
*/
@Override
public Class<? extends SqlSessionFactory> getObjectType() {
return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();
}
/**
* 是否单例
*/
@Override
public boolean isSingleton() {
return true;
}
三、Mapper接口的获取
下面就需要看下Spring是如何加载我们的Mapper接口了,Spring里面主要通过MapperScannerConfigurer类来扫描我们的mapper接口,并将它们注册到IOC容器中。
- MapperScannerConfigurer:Spring使用它来扫描我们的mapper接口,并注册到IOC中;
首先看一下MapperScannerConfigurer类的继承关系图:
重点关注一下上图红框框起来的,这是Spring扫描mapper接口的核心类:
可以看到MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,重写了postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)方法,postProcessBeanDefinitionRegistry方法允许我们通过编码的方式,改变、新增类的定义信息。 我们来看一下具体的源码:
代码语言:javascript复制//org.mybatis.spring.mapper.MapperScannerConfigurer#postProcessBeanDefinitionRegistry
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
//创建一个mapper扫描器
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
//设置一些属性,如SqlSessionFactory、BeanNameGenerator等
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.registerFilters();
//扫描具体的mapper接口,并加入到IOC容器中
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
MapperScannerConfigurer已经将Mapper扫描,并加入到IOC容器中了,那我们是如何从Spring的IOC中获取mapper接口的,这时候另外一个类:MapperFactoryBean类就发挥作用了。
代码语言:javascript复制public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
private boolean addToConfig = true;
public MapperFactoryBean() {
//intentionally empty
}
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
/**
* {@inheritDoc}
*/
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
/**
* {@inheritDoc}
*/
@Override
public Class<T> getObjectType() {
return this.mapperInterface;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isSingleton() {
return true;
}
}
我们看到,MapperFactoryBean实现了FactoryBean接口,那么在调用getBean方法获取MapperFactoryBean实例的时候,实际上调用的就是getObject方法。
代码语言:javascript复制public T getObject() throws Exception {
//具体获取流程,其实就是Mybatis自己去做了
return getSqlSession().getMapper(this.mapperInterface);
}
如上可以看到,Spring内部封装了获取mapper接口的操作,所以我们不需要去手动管理SqlSession,Spring帮我们自动管理并获取mapper接口。
四、总结
本文主要总结了Spring加载MyBatis的过程,有几个关键的类:
- SqlSessionFactoryBean:实现了InitializingBean和FactoryBean接口,主要完成的工作就是构建SqlSessionFactory对象,重点关注afterPropertiesSet()方法和getObject()方法;
- MapperScannerConfigurer:实现了BeanDefinitionRegistryPostProcessor接口,主要完成的工作就是扫描我们配置的mapper接口,并注册到IOC中。重点关注postProcessBeanDefinitionRegistry方法;
- MapperFactoryBean:实现了FactoryBean接口,重点关注getObject()方法,主要完成mapper接口的获取;
Spring加载MyBatis这个过程,其实就是把MyBatis的Mapper接口转换成Bean,注入到Spring容器的过程。
鉴于笔者水平有限,如果文章有什么错误或者需要补充的,希望小伙伴们指出来,希望这篇文章对大家有帮助。