我们接下来说:springboot是如何和mybatis进行整合的
1.首先,springboot中使用mybatis需要用到mybatis-spring-boot-start,可以理解为mybatis开发的整合springboot的jar包
有一个关键点先说明:前面也提到过,不管是mybatis和spring整合,还是和springboot整合,都需要做两个操作:
1.把当前接口和对应的mapperProxyFactory存入到knownMappers中,
2.把sql包装成mappedStatement,存入到mappedStatements这个map中
1.springboot项目中,@SpringbootApplication注解上,有一个@EnableAutoConfiguration注解,而@EnableAutoConfiguration注解又利用了@Import注解,注入了一个ImportSelector的实现类 AutoConfigurationImportSelector.class
2.AutoConfigurationImportSelector在selectImports()方法中有一行重要的代码:
List configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
这行代码内部,会从所有jar包中,META-INF/spring.factories文件中,加载EnableAutoConfiguration对应的实现类,那mybatis-spring-boot-autoconfigure.jar包中,配置了两个实现类,
1 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\ 3 org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
3.我们来说 MybatisAutoConfiguration.class
1 @org.springframework.context.annotation.Configuration 2 @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class }) 3 @ConditionalOnSingleCandidate(DataSource.class) 4 @EnableConfigurationProperties(MybatisProperties.class) 5 @AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class }) 6 public class MybatisAutoConfiguration implements InitializingBean { 7 8 9 }
这里有一个点,就是 @EnableConfigurationProperties(MybatisProperties.class);点开MybatisProperties.class文件会发现,这里面声明的就是,在application.properties配置文件中,mybatis提供的配置信息:比如mybatis.mapper-locations=classpath:mapping/*Mapper.xml;这个点,不细说了,后面可能会写一篇自定义starter的学习笔记,到时候 再详细写
4.在MybatisAutoConfiguration中有一个静态内部类 AutoConfiguredMapperScannerRegistrar 实现了ImportBeanDefinitionRegistrar;
所以,spring在refresh的时候,会执行这个类的 registerBeanDefinitions()方法,将 MapperScannerConfigurer存到了beanDefinitionMap中
1 @Override 2 public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { 3 4 5 if (!AutoConfigurationPackages.has(this.beanFactory)) { 6 logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled."); 7 return; 8 } 9 //中间删除了部分代码 10 registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition()); 11 }
5.MapperScannerConfigurer是BeanDefinitionRegistryPostProcessor的实现类,在refresh() --> invokeBeanFactoryPostProcessors(beanFactory)中,会遍历所有beanFactoryPostProcessor和BeanDefinitionRegistrtPostProcessor的实现类,依次执行postProcessorBeanDefinitionRegistrar()方法
MapperScannerConfigurer的postProcessBeanDefinitionRegistry()方法,会执行扫描方法,这里的扫描方法,和mapperScannerRegistrar的registerBeanDefinitions中的doScan是一样的,这里扫描的包是在,初始化MapperScannerConfigurer的时候,在执行完属性注入之后,调用了截图中的方法,把当前pvs中的basePackage传到MapperScannerConfigurer中,这里是如何传过去的,待研究
6.在将mapper扫描完之后,需要进行sql的解析,在和springboot整合之后,需要在配置文件中配置当前要扫描的mapper.xml文件,
mybatis.mapper-locations=classpath:mapping/*Mapper.xml
这里的mapperLocation,是在sqlSesionFactorybean中进行解析的,在第3步中的自动配置类中,通过@Bean,注入了SqlSessionFactory,
在sqlSessionFactory()方法最后,会调用factoryBean.getObject()方法,这里其实调用的就是SqlSessionFactory的getObject()方法,
1 @Override 2 public SqlSessionFactory getObject() throws Exception { 3 if (this.sqlSessionFactory == null) { 4 afterPropertiesSet(); 5 } 6 7 8 return this.sqlSessionFactory; 9 }
从afterPropertiesSet()方法,一直往下追,会追到同类中的buildSqlSessionFactory()方法,在这个方法中,判断如果当前mapperlocation不为null,就进行解析
1 if (this.mapperLocations != null) { 2 if (this.mapperLocations.length == 0) { 3 LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found."); 4 } else { 5 for (Resource mapperLocation : this.mapperLocations) { 6 if (mapperLocation == null) { 7 continue; 8 } 9 try { 10 XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), 11 targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments()); 12 xmlMapperBuilder.parse(); 13 } catch (Exception e) { 14 throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e); 15 } finally { 16 ErrorContext.instance().reset(); 17 } 18 LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'"); 19 } 20 } 21 }
在xmlMapperBuilder.parse()就是原生mybatis在解析xml文件时,需要调用的方法
7.在service中注入mapper接口,在初始化service,注入依赖的mapper接口时,还是调用的mapperFactorybean.getObject()方法来获取代理对象
springboot整合mybatis 和 spring+mybatis整合时,解析xml文件有一个区别:
spring-mybatis是利用mapperFactorybean的checkDao()方法来解析xml,put数据到mappedStatement和knowmappers
springboot是利用SqlSessionFactoryBean的getObject()来解析xml,put数据到mappedStatement和knowMappers