一、整合步骤
mybatis整合到spring一般配置如下
-
添加依赖
以maven为例,在pom.xml中添加
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
- 配置Spring的bean
<!--数据源,可选其他如Druid等-->
<bean id="dataSource" class="org.apache.ibatis.datasource.pooled.PooledDataSource"
p:driver="com.mysql.jdbc.Driver"
p:password="123456"
p:url="jdbc:mysql://localhost:3306/mybatis"
p:username="root"
/>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"
p:dataSource-ref="dataSource"
p:configLocation="classpath:mybatis-spring.xml"
p:mapperLocations="classpath:tk/mybatis/simple/mapper/*.xml"
p:typeAliasesPackage="tk.mybatis.simple.model"
/>
<!--扫描mapper接口所在包-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"
p:processPropertyPlaceHolders="true"
p:basePackage="tk.mybatis.simple.mapper"/>
整合就这么完成了,其中SqlSessionFactoryBean负责提供SqlSessionFactory,所以需要给它提供配置文件等相关信息;MapperScannerConfigurer是扫描指定包的接口,把它们转换成动态代理bean。
注:p:processPropertyPlaceHolders=“true"这句代码用于解析”${}“替换成实际的值,如果没有用到”${}",可省略。
二、源码分析
1. SqlSessionFactoryBean
其中实现了InitializingBean接口的bean,会在其初始化时调用afterPropertiesSet()方法
public void afterPropertiesSet() throws Exception {
//数据源不能为空
Assert.notNull(this.dataSource, "Property 'dataSource' is required");
//sqlSessionFactoryBuilder成员在类初始化时会实例化
Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
//configuration、configLocation不能同时配置
Assert.state(this.configuration == null && this.configLocation == null || this.configuration == null || this.configLocation == null, "Property 'configuration' and 'configLocation' can not specified with together");
this.sqlSessionFactory = this.buildSqlSessionFactory();
}
调用了buildSqlSessionFactory()方法
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
Configuration configuration;
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
//可在配置文件中配置configuration
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) {
//这种情况是在配置了mybatis配置文件所在位置
//实例化xmlConfigBuilder,供后面解析
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
} else {
...省略log
//什么都没配置,就直接实例化一个Configuration
configuration = new Configuration();
if (this.configurationProperties != null) {
configuration.setVariables(this.configurationProperties);
}
}
...省略多个属性设置
if (xmlConfigBuilder != null) {
try {
//进行Mybatis配置文件解析
xmlConfigBuilder.parse();
...省略log
} catch...
}
...
if (!isEmpty(this.mapperLocations)) {
//配置了mapperLocations就对该路径下的mapper的xml文件进行解析
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
} catch ...
}
} else {
..省略log
}
return this.sqlSessionFactoryBuilder.build(configuration);
}
XMLConfigBuilder解析流程可参考:MyBatis源码笔记(一) – 大致流程
XMLMapperBuilder解析流程可参考:MyBatis源码笔记(三) – mapper解析流程
最终调用sqlSessionFactoryBuilder的build()方法进行构建
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
2. MapperScannerConfigurer
实现BeanDefinitionRegistryPostProcessor接口的bean在初始化时会调用postProcessBeanDefinitionRegistry()方法
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
//处理${}
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
//【标记1】继承了Spring的ClassPathBeanDefinitionScanner,用于扫描类路径下的类、接口
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
//addToConfig默认为true
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);
//-----分割线-------
//【标记2】
scanner.registerFilters();
//【标记3】
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
【标记1】
public ClassPathMapperScanner(BeanDefinitionRegistry registry) {
//第二个false代表不使用默认的过滤器(默认的过滤器是过滤出带@Component、@Resource注解的类)
super(registry, false);
}
【标记2】
public void registerFilters() {
boolean acceptAllInterfaces = true;
//如果配置了annotationClass,只扫描配置的特定的注解
if (this.annotationClass != null) {
addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
acceptAllInterfaces = false;
}
// 如果配置了markerInterface,只扫描特定的接口
if (this.markerInterface != null) {
addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
@Override
protected boolean matchClassName(String className) {
return false;
}
});
acceptAllInterfaces = false;
}
//上面都没配置
if (acceptAllInterfaces) {
// 扫描所有接口
addIncludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return true;
}
});
}
// 排除 package-info.java
addExcludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
String className = metadataReader.getClassMetadata().getClassName();
return className.endsWith("package-info");
}
});
}
【标记3】
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
...省略log警告
} else {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
scan()方法是Spring原本提供的,而doScan方法被ClassPathMapperScanner覆盖重写了。
ClassPathBeanDefinitionScanner如何扫描包可参考:Spring component-scan源码分析(一)
主要分析processBeanDefinitions()方法
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
//遍历所有扫描到的BeanDefinition
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
...省略log
//构造方法参数添加为当前接口的类名
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
//实际把bean的类型改为了MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBean.getClass());
//上面提到默认为true,表示把mapper接口添加到configuration中
definition.getPropertyValues().add("addToConfig", this.addToConfig);
...
//explicitFactoryUsed标志在外部配置文件指定了
//sqlSessionFactory、sqlSessionFactoryBeanName、sqlSessionTemplateBeanName、sqlSessionTemplate其中一个时为true
if (!explicitFactoryUsed) {
...省略log
//【标记4】这句话表示当前bean会根据类型自动注入属性
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
这里对扫描出来的beanDefinitions逐个处理,把实际类型改成了MapperFactoryBean类型,所以最后实例化出来的类型是MapperFactoryBean。
回顾下Mybatis拿到mapper实现类是通过
sqlSession.getMapper(xxx.class);而MapperFactoryBean是个工厂bean,那它的getObject()方法就需要返回对应mapper接口的实现类。
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
public SqlSession getSqlSession() {
return this.sqlSession;
}
如上,getObject()方法果然通过了sqlSession.getMapper(xxx.class)来拿到对应mapper接口的实现类,而getSqlSession()方法在SqlSessionDaoSupport类中。
三、最后补充
大致流程算是走通了,现在关注下细节。
上面的DaoSupport抽象类实现了InitializingBean接口,而在afterPropertiesSet()做的事情是
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
//留给子类实现
checkDaoConfig();
try {
initDao();
}
catch (Exception ex) {
throw new BeanInitializationException("Initialization of DAO failed", ex);
}
}
MapperFactoryBean类实现了该方法
@Override
protected void checkDaoConfig() {
//调用父类
super.checkDaoConfig();
//mapperInterface是接口类型
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
//addToConfig标志在这里起作用
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
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();
}
}
}
父类SqlSessionDaoSupport的checkDaoConfig()方法
protected void checkDaoConfig() {
notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
}
这里要检测sqlSession成员不能为空,而该成员被赋值可能的地方是下面两个方法:
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSession = sqlSessionTemplate;
this.externalSqlSession = true;
}
但从上面的processBeanDefinitions()方法中可以看到,对beanDefinition的处理时只加了一个属性addToConfig,同时配置文件中的确是配了一个SqlSessionFactory的bean,那它们是怎么关联起来的呢???
答案是:【标记4】处代码,指定了MapperFactoryBean实例化时,会自动填充setter方法的属性,我们在容器中配置了SqlSessionFactory的bean,所以会调用setSqlSessionFactory()方法来注入属性。
这也是为什么在容器只有一个sqlSessionFactory实例的情况下,MapperScannerConfigurer在配置不用配置sqlSessionFactory等这些配置,全是AUTOWIRE_BY_TYPE的功劳。