细呀,IDEA还可以这样搭建半自动映射框架Mybatis
Posted GYTTking
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了细呀,IDEA还可以这样搭建半自动映射框架Mybatis相关的知识,希望对你有一定的参考价值。
- 首先我们建立一个SpringBoot工程,然后导入mybatis-spring-boot-starter依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
- 导入后发现这个依赖其实就是帮助我们导入了mybatis需要的依赖,其中和自动配置相关最重要的一个就是mybatis-spring-boot-autoconfigure
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</dependency>
</dependencies>
- MyBatis自动配置中是如何工作的
- 如上面分析自动配置的关键类我们就从mybatis-spring-boot-autoconfigure开始着手分析。
- spring.factories
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
-
从spring.factories文件看到这里通过SPI机制加载了两个类
-
MybatisAutoConfiguration
-
MybatisLanguateDriverAutoConfiguration.
-
MybatisAutoConfiguration
//表示这是一个Spring配置类
@Configuration
//这个类需要在classpath中存在SqlSessionFactory和SqlSessionFactoryBean时才生效
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class}) --
//这个类需要有一个DataSource的Canidate注册到Spring容器中
@ConditionalOnSingleCandidate(DataSource.class)
//使MybatisProperties注解类生效
@EnableConfigurationProperties({MybatisProperties.class})
//需要在DataSourceAutoConfiguration和MybatisLanguageDriverAutoConfiguration自动配置之后执行
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
public class MybatisAutoConfiguration implements InitializingBean {
}
- MybatisAutoConfiguration#sqlSessionFactory
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
//创建一个SqlSessionFactoryBean, 在mybatis-spring项目下
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
//应用Configuration对象
this.applyConfiguration(factory);
if (this.properties.getConfigurationProperties() != null) {
factory.setConfigurationProperties(this.properties.getConfigurationProperties());
}
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
if (this.databaseIdProvider != null) {
factory.setDatabaseIdProvider(this.databaseIdProvider);
}
if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
}
if (this.properties.getTypeAliasesSuperType() != null) {
factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
}
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
if (!ObjectUtils.isEmpty(this.typeHandlers)) {
factory.setTypeHandlers(this.typeHandlers);
}
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
}
Set<String> factoryPropertyNames = (Set)Stream.of((new BeanWrapperImpl(SqlSessionFactoryBean.class)).getPropertyDescriptors()).map(FeatureDescriptor::getName).collect(Collectors.toSet());
Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
factory.setScriptingLanguageDrivers(this.languageDrivers);
if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
defaultLanguageDriver = this.languageDrivers[0].getClass();
}
}
//设置默认的语言驱动类
if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
}
//这里默认会返回一个DefaultSqlSessionFactory对象
return factory.getObject();
}
- MybatisAutoConfiguration#sqlSessionTemplate
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
return executorType != null ? new SqlSessionTemplate(sqlSessionFactory, executorType) : new SqlSessionTemplate(sqlSessionFactory);
}
-
这里也就知道了MyBatis自动配置其实就是替我们完成了SqlSessionFactory和SqlSessionTempate的创建, 省去了自己导入相关依赖和配置相关Bean的麻烦.
-
MybatisLanguageDriverAutoConfiguration
-
这个类的配置是对各个语言的支持,比如Thymeleaf, Velocity,LegacyVelociy, FreeMarker等视图组件的支持。
@Configuration
@ConditionalOnClass({LanguageDriver.class})
public class MybatisLanguageDriverAutoConfiguration {
private static final String CONFIGURATION_PROPERTY_PREFIX = "mybatis.scripting-language-driver";
public MybatisLanguageDriverAutoConfiguration() {
}
@Configuration
@ConditionalOnClass({ThymeleafLanguageDriver.class})
public static class ThymeleafConfiguration {
public ThymeleafConfiguration() {
}
}
@Configuration
@ConditionalOnClass({VelocityLanguageDriver.class, VelocityLanguageDriverConfig.class})
public static class VelocityConfiguration {
public VelocityConfiguration() {
}
}
@Configuration
@ConditionalOnClass({Driver.class})
@ConditionalOnMissingClass({"org.mybatis.scripting.velocity.VelocityLanguageDriverConfig"})
public static class LegacyVelocityConfiguration {
public LegacyVelocityConfiguration() {
}
}
@Configuration
@ConditionalOnClass({FreeMarkerLanguageDriver.class, FreeMarkerLanguageDriverConfig.class})
public static class FreeMarkerConfiguration {
public FreeMarkerConfiguration() {
}
}
@Configuration
@ConditionalOnClass({FreeMarkerLanguageDriver.class})
@ConditionalOnMissingClass({"org.mybatis.scripting.freemarker.FreeMarkerLanguageDriverConfig"})
public static class LegacyFreeMarkerConfiguration {
public LegacyFreeMarkerConfiguration() {
}
}
}
-
MybatisLanguageDriverAutoConfiguration类在org.mybatis.spring.boot.autoconfigure包下,我删掉了内部静态类下的代码,为了保持这个类看起来更直观
-
自定义Mapper是如何被扫描的
-
业务开发中,我们是声明接口(Mapper),那么我们自定义的Mapper是如何被扫描的呢, 我们继续顺着MybatisAutoConfiguration代码分析,其内部包含了一个AutoConfiguredMapperScannerRegistrar的内部静态类.
-
AutoConfiguredMapperScannerRegistrar
-
registerBeanDefinitions
public static class AutoConfiguredMapperScannerRegistrar
implements BeanFactoryAware, ImportBeanDefinitionRegistrar {
private BeanFactory beanFactory;
public AutoConfiguredMapperScannerRegistrar() {
}
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
if (!AutoConfigurationPackages.has(this.beanFactory)) {
} else {
//1.获取到SpringBoot的基础包路径
List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
//2.生成一个BeanDefinition的构造器,用于构建MapperScannerConfigurer的 //BeanDefinition
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
//3.设置@Mapper注解的接口才会被当成Mapper接口
builder.addPropertyValue("annotationClass", Mapper.class);
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
//4.获取MapperScannerConfigurer的属性名称
Set<String> propertyNames = (Set)Stream.of(beanWrapper.getPropertyDescriptors()).map(FeatureDescriptor::getName).collect(Collectors.toSet());
if (propertyNames.contains("lazyInitialization")) {
builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}");
}
if (propertyNames.contains("defaultScope")) {
builder.addPropertyValue("defaultScope", "${mybatis.mapper-default-scope:}");
}
//5.这里添加一个MapperScannerConfigurer的BeanDefinition对象,也就是注入一个
//MapperScannerConfigurer对象
registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
}
}
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
}
-
AutoConfiguredMapperScannerRegistrar类是MybatisAutoConfiguration的内部静态类,位于包org.mybatis.spring.boot.autoconfigure下。
-
可以看到这个类实现了ImportBeanDefinitionRegistrar接口,ImportBeanDefinitionRegistrar接口是Spring用来动态注册Bean的,也就是会向Spring容器中注入一个BeanDefinition, 这个BeanDefinition就是MapperScannerConfigurer。
ImportBeanDefinitionRegistrar实现类只能通过其他类@Import的方式来加载,通常是配置类或者启动类,所以MybatisAutoConfiguration类下还有一个内部类MapperScannerRegistrarNotFoundConfiguration如下。
@Configuration
@Import({MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar.class})
@ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
}
- 这个方法里最后会调用接口BeanDefinitionRegistry.registerBeanDefinition, beanName是"org.mybatis.spring.mapper.MapperScannerConfigurer", registerBeanDefinition方法实际会调用DefaultListableBeanFactory.registerBeanDefinition。DefaultListableBeanFactory是BeanDefinitionRegistry接口的实现类。
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory,
BeanDefinitionRegistry,
Serializable {
}
-
AutoConfiguredMapperScannerRegistrar类和MapperScanner注解的作用是一样的,如果你没有通过以下三种配置方式扫描Mapper接口的包路径
-
配置MapperScannerConfigurer扫描器类型的Spring Bean
-
@Mapper注解
-
<mybatis: scan/>标签
-
那么这里就会通过AutoConfiguredMapperScannerRegistrar类添加一个MapperScannerConfigurer扫描器对象,去扫描SpringBoot包设置的基础包路径,也就是启动类的同级目录。 如果设置了@Mapper注解,则会当成Mapper接口解析,那么这里自动配置则不生效。
-
MapperScannerConfigurer
- MapperScannerConfigurer
- MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,BeanDefinitionRegistryPostProcessor 接口又继承了BeanFactoryPostProcessor接口, 也就是说在MapperScannerConfigurer类里需要实现这两个接口的方法。
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry var1) throws BeansException;
}
@FunctionalInterface
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory var1) throws BeansException;
}
- 在MapperScannerConfigurer类里可以看到这里只实现了postProcessBeanDefinitionRegistry。
-
BeanDefinitionRegistryPostProcessor
-
Spring里有两个用来动态注册Bean到容器中(BeanDefinitionRegistryPostProcessor和ImportBeanDefinitionRegistrar)。ImportBeanDefinitionRegistrar上文中有提到。
BeanDefinitionRegistryPostProcessor接口实现了BeanFactoryPostProcessor接口,是Spring框架的BeanDefinitionRegistry的后置处理器,用来注册额外的BeanDefinition,postProcessBeanDefinitionRegistry方法会在所有的beanDefinition被加载了,但是所有的Bean还没创建前调用。BeanDefinitionRegistryPostProcessor经常被用来注册BeanFactoryPostProcessor的BeanDefinition。 -
postProcessBeanDefinitionRegistry
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
this.processPropertyPlaceHolders();
}
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);
scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
if (StringUtils.hasText(this.lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(this.lazyInitialization));
}
if (StringUtils.hasText(this.defaultScope)) {
scanner.setDefaultScope(this.defaultScope);
}
scanner.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \\t\\n"));
}
}
-
MapperScannerConfigurer在包org.mybatis.spring.mapper下.
-
这里会调用ClassPathMapperScanner.scan(),而ClassPathMapperScanner又继承了ClassPathBeanDefinitionScanner,所以这里scan()会调用ClassPathBeanDefinitionScanner.scan(), 而ClassPathBeanDefinitionScanner.scan() 第二句代码又调用了this.doScan(basePackages), this.doScan()又调用了ClassPathMapperScanner.doScan(), 而这个方法第一句代码又调用了super.doScan(basePackages),父子类来回互相调用,有点晕头转向的。
-
org.mybatis.spring.mapper.ClassPathMapperScanner
org.springframework.context.annotation.ClassPathBeanDefinitionScanner这个类在spring-context.jar -
ClassPathMapperScanner
-
ClassPathBeanDefinitionScanner#scan
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
this.doScan(basePackages);
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return this.registry.getBeanDefinitionCount() - beanCountAtScanStart;
}
- ClassPathMapperScanner#doScan()
这个方法里在mybatis自动配置算比较重要的一个方法,也就是帮助我们自动配置MapperFactoryBean, 会把根据basePackage注册进Spring容器的BeanDefinition的beanClass设置成MapperFactoryBean。
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
LOGGER.warn(() -> {
return "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.";
});
} else {
//这是一个关键方法,会处理已经注册进Spring容器的beanDefinition,也就是会把
//已经注册进Spring容器的beanDefinitiond的beanClass为MapperFactoryBean
this.processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
- ClassPathBeanDefinitionScanner#doScan()
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet();
String[] var3 = basePackages;
int var4 = basePackages.length;
for(int var5 = 0; var5 < var4; ++var5) {
String basePackage = var3[var5];
//这个方法会扫描指定basePackage下被@Mapper注解标注的接口
Set<BeanDefinition> candidates = this.findCandidateComponents(basePackage);
Iterator var8 = candidates.iterator();
while(var8.hasNext()) {
BeanDefinition candidate = (BeanDefinition)var8.next细呀,IDEA还可以这样搭建半自动映射框架Mybatis
在 idea 下搭建的第一个MyBatis项目及增删改查用法
SSM框架——使用MyBatis Generator自动创建代码