背景知识
1 MappedStatement是mybatis操作sql语句的持久层对象,其id由注解模式的{methodName}或者XML模式的{CRUD标签的id}确定,且是唯一的
2 Mybatis对每个CRUD语句都会生成唯一的MappedStatement保存至Configuration的mappedStatements(Map集合)中
3 Mybatis提供注解模式和XML模式生成MappedStatement,在两者同时存在的情况下,注解模式的MappedStatement会覆盖同id的XML模式的MappedStatement
4 注解模式生成MappedStatement的途径有两个,一个是在其同目录下存在与类名一致的mapper文件;另一个是其方法名实现了CRUD的注解,如果被注解的方法名与mapper文件存在同id,遵循第3点特性
5 (本文解析重点)XML模式生成的MappedStatement,还必须拥有对应的mapperInterface接口供Service层调用,即mapperInterface接口是需要注册到Configuration的MapperRegistry对象中,方便Service层找寻调用
Spring Mybatis 接口注入老配置
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="com.test.sqlmapper.UserMapper"/>
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
上述的配置是针对单个的mapperInterface注入到应用程序中,试想如果有很多的接口则会导致Spring主配置文件臃肿,所以上述的办法已过时
Spring Mybatis 接口注入新配置
其实新的配置就是动态的将dao接口,转化成MapperFactoryBean包装l类加载到spring容器中
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="org.mybatis.spring.sample.mapper" />
<!-- optional unless there are multiple session factories defined -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
MapperScannerConfigurer
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
private String basePackage;
private boolean addToConfig = true;
private SqlSessionFactory sqlSessionFactory;
private SqlSessionTemplate sqlSessionTemplate;
private String sqlSessionFactoryBeanName;
private String sqlSessionTemplateBeanName;
private Class<? extends Annotation> annotationClass;
private Class<?> markerInterface;
private ApplicationContext applicationContext;
private String beanName;
private boolean processPropertyPlaceHolders;
private BeanNameGenerator nameGenerator;
....
}
其源码上的注释其实写的很清楚了,注释篇幅过长,就不在这里展示了,稍微提下这个类的相关使用:
basePackage 基本属性,接口类扫描的包路径,支持,;分隔
sqlSessionFactoryBeanName 当有多个SqlSessionFactory环境时,官方通过其来指定加载特定的sqlSessionFactory,value即为bean的id值(建议使用此属性)
sqlSessionFactoty 默认是不用填的,其会去寻找id为sqlSessionFactory的sqlSessionFactory实例,sqlSessionTemplate的操作类似
annotationClass 注解类,其会去Spring环境下寻找拥有此注解的接口类,并忽略basePackage的属性,默认为null
markerInterface 父类接口类,其会去寻找继承此接口类的子接口类但不包括父类接口,并忽略basePackage的属性,与annotationClass并存,默认为null
MapperScannerConfigurer#postProcessBeanDefinitionRegistry()
直接进入其关键方法,观察下是如何进行扫描的
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
//支持${basePackage}形式
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
//base classpath environment to scan
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);
//base annotationClass and markerInterface properties to register interface filters
scanner.registerFilters();
// scan
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
上述代码也就是设置相应的属性给ClassPathMapperScanner,具体的如何扫描我们继续往下看
ClassPathBeanDefinitionScanner
扫描的具体操作由ClassPathMapperScanner的父类ClassPathBeanDefinitionScanner来完成,我们简单的看下其中的逻辑
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
for (String basePackage : basePackages) {
// 找寻classpath对应的目录,其中的解析帮助类为PathMatchingResourcePatternResolver
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
// 遍历
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
// 设置基本的属性,比如lazy-init/autowire-mode等
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
// 对针对@mapper注解的bean
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
// 确保不重复注册到bean工厂
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
上述代码最关键的便是找寻对应目录的所有interface接口,其是通过PathMatchingResourcePatternResolver帮助类来完成的,后续笔者独立成篇加以分析
对找寻的beanDefinitions集合过程中也会进行filters过滤,即用到了annotationClass和markerInterface属性
ClassPathMapperScanner
针对上述的符合条件后获取的beanDefinitions集合,子类便要进行最后的加工处理
/**
* Calls the parent search that will search and register all the candidates.
* Then the registered objects are post processed to set them as
* MapperFactoryBeans
*/
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
//由父类去找到符合条件的interface类,并转化为bean类
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 {
for (BeanDefinitionHolder holder : beanDefinitions) {
GenericBeanDefinition 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包装成MapperFactoryBean,beanClass设置为其内部属性MapperInterface
definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
definition.setBeanClass(MapperFactoryBean.class);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
//根据sqlsessionFactoryBeanName寻找运行状态的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;
}
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;
}
//当没有指定SqlSession对象,则设置MapperFactoryBean自动去找寻
if (!explicitFactoryUsed) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
return beanDefinitions;
}
根据上述代码得知,其最终被包装的类为MapperFactoryBean,由其完成最终的MappedStatement关联
MapperFactoryBean
笔者此处只关注其关键方法checkDaoConfig(),源码如下
/**
* {@inheritDoc}
*/
@Override
protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = getSqlSession().getConfiguration();
// 判断mapperInterface接口是否已被注册过
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
// 此处就跟mybatis加载主配置文件时对mapper节点指定package属性或者mapperClass属性的入口是一样的
configuration.addMapper(this.mapperInterface);
} catch (Throwable t) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", t);
throw new IllegalArgumentException(t);
} finally {
ErrorContext.instance().reset();
}
}
}
总结
1MapperScannerConfigurer类主要实现将basePackage包下的所有接口类注册到Configuration的内部属性MapperRegister#knowMappers(HashMap集合)中
2MapperScannerConfigurer类默认情况下在形成MappedStatement的过程中会优先去找寻与接口同目录下的XML文件来加载生成。
如果想应用XML配置文件且可以任意放置,则可以结合sqlSessionFactoryBean的mapperLocations属性来完成自由化绑定的过程
3MappedStatement对象的生成与MapperInterfaces接口类是一一对应的
MapperInterfaces接口类可通过注解例如@Select方式生成注解,即脱离XML配置方式
MapperInterfaces接口类如果在SqlsessionFactory不使用mapperLocations属性时且不使用注解方式,则必须在其同目录下有同名字的XML mapper文件,否则无法访问数据库;
反之使用mapperLocations属性,则mapper文件只需放在classpath路径下即可
网友评论