引言
- MyBatis是一个开源的Java持久层框架,用于简化数据库操作。它是一种基于映射文件实现的ORM(对象关系映射)框架,可以将Java对象和数据库表之间的映射关系定义在XML文件中。
- 目前我们项目使用Mybatis主要是通过mybatis-spring整合mybatis,所以需要对源码有相关的了解,方便排查问题。
示例
- 使用mybatis-spring时只需要在pom中引入相关依赖即可,下面的依赖主要是在spring-boot中使用,默认帮我们引入了:mybatis、mybatis-spring、myatis-spring-boot-autoconfigure。可以通过spring管理mybatis的bean活动周期。
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.2.0</version>
</dependency>
- 在基础使用mybatis时需要在数据访问对象(DAO)中手工编写SqlSessionDaoSupport 或 SqlSessionTemplate 的代码。而mybatis-spring支持自动创建线程安全的映射器,这样就可以直接注入到其他bean中
- 使用mybatis-spring映射器时不需要一个一个注册,mybatis-spring可以对类路径进行扫描发现所有的映射器。主要有以下几种方法:
- 使用mybatis:scan/元素
- 使用@MapperScan注解
- 在spring xml配置文件中注册一个MapperScannerConfigurer
源码分析
mybatis:scan
使用示例
base-package 属性允许你设置映射器接口文件的基础包。通过使用逗号或分号分隔,你可以设置多个包。并且会在你所指定的包中递归搜索映射器。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd">
<mybatis:scan base-package="org.mybatis.spring.sample.mapper" />
<!-- ... -->
</beans>
源码分析
- mybatis:scan功能的实现原理主要依赖于 MyBatis 提供的 MapperScannerConfigurer 类,该类实现了 Spring 的 BeanDefinitionRegistryPostProcessor 接口,并重写了 postProcessBeanDefinitionRegistry() 方法。在 Spring 容器启动时,该方法会被调用,并使用 MyBatis 自己的扫描规则对指定包下的接口进行扫描。扫描结果会以 BeanDefinition 对象的形式注册到 Spring 容器中,从而实现在运行时动态生成 SQL 映射接口的实例。
* 在META-INF/spring.handlers配置中注入NamespaceHandler,调用的属性文件包含 XML 模式 URI 到 命名空间处理程序类
http\://mybatis.org/schema/mybatis-spring=org.mybatis.spring.config.NamespaceHandler
- 在META-INF/spring.schemas配置xml文件的命名空间,防止Spring需要检查架构文件的Internet访问。
http\://mybatis.org/schema/mybatis-spring-1.2.xsd=org/mybatis/spring/config/mybatis-spring-1.2.xsd
http\://mybatis.org/schema/mybatis-spring.xsd=org/mybatis/spring/config/mybatis-spring-1.2.xsd
@MapperScan
- @MapperScan是MyBatis-Spring的一个注解,用于扫描指定包下的所有Mapper接口并生成对应的Mapper实现类。
- @MapperScan支持以下方法配置
/**
* 属性的别名,允许更简洁
*/
String[] value() default {};
/**
* 用于扫描MyBatis接口的基本包。请注意,只有接口
*/
String[] basePackages() default {};
/**
* 替代basePackages,扫描配置中的每一个类
*/
Class<?>[] basePackageClasses() default {};
/**
* 命名检测到的组件
*/
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
/**
* 注解扫描公共方法
*/
Class<? extends Annotation> annotationClass() default Annotation.class;
/**
* 指定扫描仪将搜索的父级。
*/
Class<?> markerInterface() default Class.class;
/**
* 指定在存在的情况下要使用的
*/
String sqlSessionTemplateRef() default "";
/**
* 多数据源时使用
*/
String sqlSessionFactoryRef() default "";
/**
* 指定一个自定义MapperFactoryBean,以将mybatis代理作为springbean返回
*
*/
Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
- 在MapperScannerRegistrar.registerBeanDefinitions扫描注解类
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// this check is needed in Spring 3.1
if (resourceLoader != null) {
scanner.setResourceLoader(resourceLoader);
}
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
scanner.setAnnotationClass(annotationClass);
}
Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
scanner.setMarkerInterface(markerInterface);
}
Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
}
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
}
scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
List<String> basePackages = new ArrayList<String>();
for (String pkg : annoAttrs.getStringArray("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (String pkg : annoAttrs.getStringArray("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
//将父扫描程序配置为搜索正确的接口。它可以搜索所有接口,也可以只搜索扩展标记接口的接口或/和用annotationClass注释的接口
scanner.registerFilters();
// 调用将搜索并注册所有候选者的父搜索。然后对注册的对象进行后处理,将它们设置为MapperFactoryBeans
scanner.doScan(StringUtils.toStringArray(basePackages));
}
- ClassPathMapperScanner.doScan扫描所有dao类并注册为bean
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
//在指定的基本包中执行扫描,返回注册的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 {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
- ClassPathMapperScanner.processBeanDefinitions
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");
}
// mapper接口是bean的原始类
// 但是,bean的实际类是MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
definition.setBeanClass(this.mapperFactoryBean.getClass());
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
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;
}
if (!explicitFactoryUsed) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
// 自动装配,通过set方法,并且再根据bean的类型,注入属性
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
总结
- 通过学习mybatis-spring扫描映射器的源码学习到了通过spring提供的通过xml扩展的方式自定义xml bean解析器,并集成到Spring Ioc容器中。主要是通过以下方式:
-
- 自定义创建xml schema文件
-
- 自定义处理器(实现NamespaceHandler接口)
-
- 自定义解析器(实现BeanDefinitionParse接口)
-
- 注册到Spring容器中
-
- 学习mybatis是如何通过继承ClassPathBeanDefinitionScanner实现指定包下的类通过一定规则过滤后 将Class 信息包装成 BeanDefinition 的形式注册到IOC容器中。