Mybatis Spring集成原理分析
Mybatis Spring集成原理分析
作者:IT王小二
下载整合源码包:https://github.com/mybatis/spring,执行 mvn clean install -Dmaven.test.skip=true
命令将其安装到本地,然后就可以在spring项目中引用了。
一、集成配置过程
1、准备 spring 项目一个,添加依赖,当然,实际开发使用正常版本即可,不需要使用本地源码版本。
<!-- mybatis与spring对接依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.3-SNAPSHOT</version>
</dependency>
2、在 applicationContext.xml 中配置几个重要的属性。
- 配置 SqlSessionFactoryBean
- dataSource:用于配置数据源,该属性为必选项,必须通过这个属性配置据源。
- mapper Locations : 配置 SqlSessionFactoryBean 扫描 XML 映射文件的路径。
- configLocation:用于配置mybatisConfig XML的路径,除了数据源外,对MyBati的各种配置仍然可以通过这种方式进行,并且配置 MyBatis settings 时只能使用这种方式。但配置文件中任意环境,数据源 和 MyBatis 的事务管理器都会被忽略。
- typeAliasesPackage: 配置包中类的别名,配置后,包中的类在 XML 映射文件中使用时可以省略包名部分 ,直接使用类名。这个配置不支持 Ant 风格的路径,当需要配置多个包路径时可以使用分号或逗号进行分隔。
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<!-- 基本属性 url、user、password -->
<property name="driverClassName" value="${jdbc_driver}" />
<property name="url" value="${jdbc_url}" />
<property name="username" value="${jdbc_username}" />
<property name="password" value="${jdbc_password}" />
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="1" />
<property name="minIdle" value="1" />
<property name="maxActive" value="20" />
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="60000" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="validationQuery" value="SELECT 'x'" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<!-- 配置监控统计拦截的filters -->
<property name="filters" value="stat" />
</bean>
<!-- 配置sqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="typeAliasesPackage" value="com.study.mybatis.entity" />
<property name="mapperLocations" value="classpath:mapping/*.xml" />
</bean>
- 配置 MapperScannerConfigurer,通过 MapperScannerConfigurer 类自动扫描所有的 Mapper 接口,使用时可以直接注入接口,MapperScannerConfigurer 中常配置以下两个属性。
- basePackage:用于配置基本的包路径。可以使用分号或逗号作为分隔符设置多于一个的包路径,每个映射器将会在指定的包路径中递归地被搜索到 。
- annotationClass:用于过滤被扫描的接口,如果设置了该属性,那么 MyBatis 的接口只有包含该注解才会被扫描进去。
<!-- DAO接口所在包名,Spring会自动查找其下的类 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.enjoylearning.mybatis.mapper" />
</bean>
- 配置事务,让 Mybatis 集成 spring 的事务。
<!-- (事务管理)transaction manager -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
<qualifier value="transactionManager" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
二、MyBatis Spring 集成原理分析
1. SqlSessionFactoryBean源码分析
前面提到了配置 SqlSessionFactoryBean,那么我们来看下 SqlSessionFactoryBean,其实在这个类加载过程中就是执行我描述 MyBatis 流程的第一个阶段:初始化阶段,其中几个重要方法如下:
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
/**
* 在spring容器中创建全局唯一的sqlSessionFactory
*/
@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");
// 封装了对MyBatis初始化阶段
this.sqlSessionFactory = buildSqlSessionFactory();
}
/**
*
* 将SqlSessionFactory对象注入spring容器
*/
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
}
- 首先,SqlSessionFactoryBean 实现了 InitializingBean 接口,那么容器在初始化完成 SqlSessionFactoryBean 之后必然会调用 afterPropertiesSet() 方法,其中调用的 buildSqlSessionFactory()方法实际是对 MyBatis 初始化加载配置阶段的封装。
- 其次,SqlSessionFactoryBean 实现了 FactoryBean 接口,当在容器中配置 FactoryBean 的实现类时,并不是将该 FactoryBean 注入到容器,而是调用 FactoryBean 的 getObject 方法产生的实例对象注入容器。
2. MapperScannerConfigurer源码分析
这个就是为了帮助我们进行实现 mapper接口类,这个对应的就是我描述的 MyBatis 流程的第二个阶段:代理封装阶段。
MapperFactoryBean
首先来说说另外一个类:MapperFactoryBean,除了 MapperScannerConfigurer 配置方式以外,还有一种配置方式,不过我们一般都不会这么使用,但是实际上真正帮助 Spring 生成 Mapper 接口实现类的恰恰就是它。
<bean id="tUserMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="com.study.mybatis.mapper.TUserMapper"></property>
<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean>
那么看下 MapperFactoryBean 里面有什么东西。
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;
}
/**
* MapperFactoryBean在容器初始化时,要确保mapper接口被注册到mapperRegistry
*/
@Override
protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
// 通过SqlSession从容器中拿到configuration
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
// 如果mapperRegistry中不包含当前接口的动态代理工厂,则添加一个
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();
}
}
}
/**
* 通过在容器中的mapperRegistry,返回当前mapper接口的动态代理
*
* {@inheritDoc}
*/
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
// 省略其他代码
}
MapperFactoryBean 实现了 FactoryBean 接口,getObject 方法实际是封装了 MyBatis 的第二阶段,注入容器的是 SqlSession 实例化的 Mapper 接口的实现类。
MapperScannerConfigurer
由于使用 MapperFactoryBean 每个接口都要维护一个 mapperInterface 过于麻烦,且不好维护,所以就是用这个来扫描包下面的接口,然后为其生成 MapperFactoryBean,来看下 MapperScannerConfigurer 的源码。
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
// 占位符处理
processPropertyPlaceHolders();
}
// 实例化ClassPathMapperScanner,并对scanner相关属性进行配置
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);
// 根据上述配置,生成过滤器,只扫描合条件的class
scanner.registerFilters();
// 扫描指定的包以及其子包
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
// 省略其他代码
}
看到实现了 BeanDefinitionRegistryPostProcessor 接口,因此可以对 Bean 的结构调整之后再注入容器。那 MapperScannerConfigurer 在扫描完这些 mapper 接口之后,主要是将 Mapper 接口一个个的转换成 MapperFactoryBean 之后注入容器的。
跟踪一下 scanner.scan,最后发现具体转换代码在 org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions
/**
*处理扫描得到的BeanDefinitionHolder集合,将集合中的每一个mapper接口转换成MapperFactoryBean后,注册至spring容器
*/
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
// 遍历集合
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
}
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
// 增加一个构造方法,接口类型作为构造函数的入参
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
// 将bean的类型转换成mapperFactoryBean
definition.setBeanClass(this.mapperFactoryBean.getClass());
// 增加addToConfig属性
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
// 增加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;
}
// 增加sqlSessionTemplate属性
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
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;
}
// 修改自动注入的方式 bytype
if (!explicitFactoryUsed) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}