当前位置:   article > 正文

mybatis与spring boot的集成_org.mybatis.spring.boot

org.mybatis.spring.boot

前言

MyBatis提供了整合到 Spring Boot 的方案 mybatis-spring-boot-starter,能够让你快速的在 Spring Boot 上面使用 MyBatis,那么我们来看看这个 mybatis-spring-boot-starter 是如何将 MyBatis 集成到 Spring Boot中的。

1.mybatis的自动装配

引入mybatis-spring-boot-starter包。

  1. <dependency>
  2. <groupId>org.mybatis.spring.boot</groupId>
  3. <artifactId>mybatis-spring-boot-starter</artifactId>
  4. <version>2.0.0</version>
  5. </dependency>

引入这个包后,spring就会自动将mybatis相关配置注册到容器,然后,我们就可以直接使用mybatis操作数据库了。在此之前我们还需要做以下操作:

1.1.配置数据源

在使用之前,需要配置一个数据源,用于连接数据库,这个版本的数据源默认用的是Hikari,在org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration这个自动装配类里面,内置了3种数据源:tomcat-jdbc、Hikari和Dbcp2,以Hikari为例来看下源码:

  1. /**
  2. * Hikari DataSource configuration.
  3. */
  4. @ConditionalOnClass(HikariDataSource.class)
  5. @ConditionalOnMissingBean(DataSource.class)
  6. @ConditionalOnProperty(name = "spring.datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource", matchIfMissing = true)
  7. static class Hikari {
  8. @Bean
  9. @ConfigurationProperties(prefix = "spring.datasource.hikari")
  10. public HikariDataSource dataSource(DataSourceProperties properties) {
  11. HikariDataSource dataSource = createDataSource(properties,
  12. HikariDataSource.class);
  13. if (StringUtils.hasText(properties.getName())) {
  14. dataSource.setPoolName(properties.getName());
  15. }
  16. return dataSource;
  17. }
  18. }

满足以上三个条件,就会创建数据源,@ConditionalOnClass(HikariDataSource.class)这个是说类路径下需要有HikariDataSource类,也就是需要引HikariCP包,看下依赖关系:

可以看到mybatis已经把这个包引进来了,所以这个条件是满足的,再看@ConditionalOnMissingBean(DataSource.class)这个条件,没啥好说的,意思就是有了就不创建了,也意味着项目下,只会有一个数据源,那么问题来了,多数据源怎么搞呢?这个后面再说。再看第三个条件@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource", matchIfMissing = true),这个是说配置文件的数据源类型是com.zaxxer.hikari.HikariDataSource,主要看matchIfMissing=true这个属性,意思就是配置文件没有指定数据源类型,哪就是满足条件,那就是说,你没有指定用哪种数据源的话,哪就默认这个了。

所以,经过以上过程,就可以创建数据源了,还要稍等下,数据库的用户名和密码啥的还没设置,这个是省不了的,数据库相关的属性,都在org.springframework.boot.autoconfigure.jdbc.DataSourceProperties这个类里面,常用的配置如下:

spring:
  datasource:
    url: "jdbc:mysql://192.168.43.61:3306/cib"
    username: icbc
    password: icbc

只需要指定这三个就可以了,像driverClassName这些,都可以不指定,spring会分析url,从url里面把mysql解析出来,就自动匹配到mysql驱动了,所以说,spring boot还是做了很多事情,尽量让我们省事,说到mysql,这里还少个mysql驱动的包,需要引入下:

  1. <dependency>
  2. <groupId>mysql</groupId>
  3. <artifactId>mysql-connector-java</artifactId>
  4. </dependency>

这里没有写版本,使用的是spring boot指定的版本,需要在pom里面指定spring boot为父pom:

  1. <parent>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-parent</artifactId>
  4. <version>2.1.3.RELEASE</version>
  5. </parent>

1.2.创建mapper

  1. @Mapper
  2. public interface CityMapper {
  3. @Select("SELECT * FROM CITY WHERE state = #{state}")
  4. City findByState(@Param("state") String state);
  5. }

1.3.启动程序,运行sql

  1. @SpringBootApplication
  2. public class SampleMybatisApplication implements CommandLineRunner {
  3. private final CityMapper cityMapper;
  4. public SampleMybatisApplication(CityMapper cityMapper) {
  5. this.cityMapper = cityMapper;
  6. }
  7. public static void main(String[] args) {
  8. SpringApplication.run(SampleMybatisApplication.class, args);
  9. }
  10. @Override
  11. public void run(String... args) throws Exception {
  12. System.out.println(this.cityMapper.findByState("CA"));
  13. }
  14. }

这就是最简洁的方式了。以上都是通过注解的方式,其中Mapper用注解的方式,在对于稍微复杂的语句就会力不从心并且会显得混乱。所以,如果你需要做很复杂的事情,那么最好使用 XML 来映射语句。还有一点,就是Mapper很多的情况下,每个接口上都写注解,也挺啰嗦,所以也可以指定扫描的包。

这样就需要增加mybatis的一些配置,就不全使用mybatis的默认配置了。

mybatis:
  type-aliases-package: com.jverson.dao.entity  #类型别名包
  mapper-locations: classpath*:mybatis/mapper/*.xml,classpath*:com.jverson.dao/log/*.xml  #Mapper的xml文件
  config-locations: classpath:mybatis/mybatis-config.xml  #mybatis的配置文件

包扫描,可以在启动程序那块,加一句这个注解:@MapperScan("com.jverson.mapper")。

2.mybatis的自动装配过程

2.1.mybatis starter的装配

spring boot注册starter包中的bean的机制是读取starter包的spring.factories这个文件,这个文件会指定starter包的自动装配的类:

 org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration这个就是mybatis的自动装配的类,这个类是一个配置类,并声明了SqlSessionFactory和SqlSessionTemplate这两个bean,从这个类开始,便进入了spring的加载bean过程,我们看下spring的加载过程。

2.1.1.spring的加载bean过程

第一步:从xml文件或者注解获取bean的定义信息,并注册到BeanFactory(容器)

第二步:执行BeanFactory的后置处理器,在这一步可以操作bean的定义信息

第三步:注册Bean的后置处理器BeanPostProcessor,这个BeanPostProcessor有两个方法:postProcessBeforeInitialization和postProcessAfterInitialization,这两个方法会分别在每个bean实例初始化(afterPropertiesSet或init方法)前和后调用,Spring容器通过BeanPostProcessor给了我们一个机会对Spring管理的bean进行再加工。可以修改bean的属性,可以给bean生成一个动态代理实例等等。一些Spring AOP的底层处理也是通过实现BeanPostProcessor来执行代理包装逻辑的。

第四步:创建所有的 singletons bean(lazy-init 的除外),包括bean实例化、填充属性和初始化

以上是大概步骤,具体详细会有很多细节,可以参考

spring-mvc源码-bean定义加载_matt8的专栏-CSDN博客https://blog.csdn.net/matt8/article/details/106352083?spm=1001.2014.3001.5501

2.2.SqlSessionFactory和SqlSessionTemplate的装配

先看下这两个bean是干嘛的。

SqlSessionFactory是单个数据库映射关系经过编译后的内存镜像,主要作用是创建SqlSession。SqlSessionFactory是线程安全的,一旦被创建,在整个应用程序执行期间都会存在。创建SqlSessionFactory很消耗数据库资源,如果多次创建同一数据库的SqlSessionFactory,此数据库的资源很容易被耗尽。尽量使一个数据库只对应一个SqlSessionFactory,构建SqlSessionFactory时,通常使用单例模式。

SqlSessionTemplate是用来操作数据库的,提供了增删改查方法,它依赖于SqlSessionFactory,它相当于是一次和数据库的请求,它从SqlSessionFactory里面获取数据库连接。

SqlSessionFactory的创建很简单,SqlSessionTemplate这个创建有个地方要注意下,是这个构造方法创建的。

  1. public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
  2. PersistenceExceptionTranslator exceptionTranslator) {
  3. notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
  4. notNull(executorType, "Property 'executorType' is required");
  5. this.sqlSessionFactory = sqlSessionFactory;
  6. this.executorType = executorType;
  7. this.exceptionTranslator = exceptionTranslator;
  8. this.sqlSessionProxy = (SqlSession) newProxyInstance(
  9. SqlSessionFactory.class.getClassLoader(),
  10. new Class[] { SqlSession.class },
  11. new SqlSessionInterceptor());
  12. }

主要看sqlSessionProxy这个字段,这个是数据库的连接对象,所有的增删改查操作都是通过它完成的,这里返回的是一个代理对象,对SqlSession接口一个实现,看下代理的增强逻辑SqlSessionInterceptor()的实现。

  1. private class SqlSessionInterceptor implements InvocationHandler {
  2. @Override
  3. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  4. SqlSession sqlSession = getSqlSession(
  5. SqlSessionTemplate.this.sqlSessionFactory,
  6. SqlSessionTemplate.this.executorType,
  7. SqlSessionTemplate.this.exceptionTranslator);
  8. try {
  9. Object result = method.invoke(sqlSession, args);
  10. if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
  11. // force commit even on non-dirty sessions because some databases require
  12. // a commit/rollback before calling close()
  13. sqlSession.commit(true);
  14. }
  15. return result;
  16. } catch (Throwable t) {
  17. Throwable unwrapped = unwrapThrowable(t);
  18. if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
  19. // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
  20. closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
  21. sqlSession = null;
  22. Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
  23. if (translated != null) {
  24. unwrapped = translated;
  25. }
  26. }
  27. throw unwrapped;
  28. } finally {
  29. if (sqlSession != null) {
  30. closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
  31. }
  32. }
  33. }
  34. }

1、先获取了SqlSession对象,这个对象实际上返回的是DefaultSqlSession对象

2、通过Object result = method.invoke(sqlSession, args)反射执行sqlSession的方法,其实就是执行DefaultSqlSession的方法

3、如果有事务,就执行sqlSession.commit(true),其实也是执行DefaultSqlSession的commit方法

4、关闭数据库连接

其实,这个代理逻辑就是对DefaultSqlSession对象方法的一个包装,最终执行数据库操作的还是DefaultSqlSession这个对象,org.mybatis.spring.SqlSessionTemplate这个类提供了数据库操作的方法,都是通过sqlSessionProxy这个代理对象完成的,这个类也是mapper执行数据库调用的方法,大概是这么一个调用关系。

mapper -> SqlSessionTemplate -> sqlSessionProxy -> DefaultSqlSession

spring容器中有了SqlSessionFactory和SqlSessionTemplate这两个bean后,就可以操作数据库了,可以直接注入SqlSessionTemplate这个bean来操作数据库,但是我们一般都是通过mapper来操作的数据库,SqlSessionTemplate的注入是mybatis帮我们处理了。

2.3.mapper的注册

mapper都是接口,自然就能想到,mybatis肯定是通过代理的方式,给mapper接口生成了代理对象,实际的操作都是代理对象执行的,也的确如此,这个代理对象的类就是MapperFactoryBean。

mapper装配的入口有两个,一个是在org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.MapperScannerRegistrarNotFoundConfiguration这里,这个是用于装配@Mapper这个注解的mapper。另一个是用于装配@MapperScan这个注解,这个注解引入了一个mapper扫描注册类,用于注册bean信息到容器。

2.3.1.@Mapper注册

  1. @org.springframework.context.annotation.Configuration
  2. @Import({ AutoConfiguredMapperScannerRegistrar.class })
  3. @ConditionalOnMissingBean(MapperFactoryBean.class)
  4. public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
  5. @Override
  6. public void afterPropertiesSet() {
  7. logger.debug("No {} found.", MapperFactoryBean.class.getName());
  8. }
  9. }

看@Import({ AutoConfiguredMapperScannerRegistrar.class }),引入了一个mapper扫描注册类,主要逻辑也在AutoConfiguredMapperScannerRegistrar里。

 这个类实现了ImportBeanDefinitionRegistrar这个接口,并实现了其的registerBeanDefinitions方法,这个方法就是用于注册bean信息的,这里注册的是mapper的信息,主要逻辑就是通过org.mybatis.spring.mapper.ClassPathMapperScanner#doScan这个方法扫描项目包下的使用@Mapper的类或接口,然后进行bean信息的注册,核心逻辑在org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions在这个方法里,这里说下@MapperScan也是走的这个逻辑。

  1. GenericBeanDefinition definition;
  2. for (BeanDefinitionHolder holder : beanDefinitions) {
  3. definition = (GenericBeanDefinition) holder.getBeanDefinition();
  4. String beanClassName = definition.getBeanClassName();
  5. LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName()
  6. + "' and '" + beanClassName + "' mapperInterface");
  7. // the mapper interface is the original class of the bean
  8. // but, the actual class of the bean is MapperFactoryBean
  9. //设置构造函数的参数,这里的参数值beanClassName为原始类,也就是mapper接口,这里设置了构造函数的参数值,那么后面实例化的时候,会调用带参数的构造函数来new对象,也就是MapperFactoryBean(Class<T> mapperInterface)这个方法,因为最终使用的是代理类,所以最终new的也是代理类
  10. definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
  11. //设置当前bean的类为MapperFactoryBean类,我们知道当前其实是一个mapper接口,这么设置后,后面实例化的就是MapperFactoryBean
  12. definition.setBeanClass(this.mapperFactoryBean.getClass());
  13. definition.getPropertyValues().add("addToConfig", this.addToConfig);
  14. boolean explicitFactoryUsed = false;
  15. if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
  16. //设置sqlSessionFactory,@MapperScan会走到这个逻辑,类似@MapperScan(basePackages = {"com.dao.mapper"},sqlSessionFactoryRef = "dbSqlSessionFactory"),这里的sqlSessionFactoryBeanName就是sqlSessionFactoryRef的值,因为当前是注册bean信息阶段,所以还没有sqlSessionFactory实例,这里只是设置了名字,通过RuntimeBeanReference来告诉后续实例化时,使用sqlSessionFactoryBeanName这个名字来注入sqlSessionFactory
  17. definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
  18. explicitFactoryUsed = true;
  19. } else if (this.sqlSessionFactory != null) {
  20. //目前还没看到哪里能走到这个逻辑
  21. definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
  22. explicitFactoryUsed = true;
  23. }
  24. //设置sqlSessionTemplate,逻辑与sqlSessionFactory类似
  25. if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
  26. if (explicitFactoryUsed) {
  27. LOGGER.warn(() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
  28. }
  29. definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
  30. explicitFactoryUsed = true;
  31. } else if (this.sqlSessionTemplate != null) {
  32. if (explicitFactoryUsed) {
  33. LOGGER.warn(() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
  34. }
  35. definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
  36. explicitFactoryUsed = true;
  37. }
  38. if (!explicitFactoryUsed) {
  39. LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
  40. //自动装配的情况下会走到这里,因为前面指定sqlSessionFactory或sqlSessionTemplate的逻辑都不会走,这时候会设置注入的方式为类型注入,注意,这个和Spring注解(@Autowire)注入的方式不是一回事,MapperFactoryBean本身也没有使用任何注解,我们知道MapperFactoryBean有一个sqlSessionTemplate属性需要注入,后续实例化MapperFactoryBean时,在注入ssqlSessionTemplate时,会根据这里设置的类型注入的方式进行注入,这种注入方式会调用setter方法进行注入,也就是会调用org.mybatis.spring.support.SqlSessionDaoSupport#setSqlSessionTemplate这个方法注入,我们知道sqlSessionTemplate在自动配置的情况下,已经默认创建了,所以根据类型找到的就是这个
  41. definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
  42. }
  43. }

上面的逻辑,里面做了详细的注释,里面提到一点注入方式,这里说下:

spring种5中注入模型

1、AUTOWIRE_NO = 0;(不使用自动注入,spring默认的值,一般使用的 @AutoWired ,@Resource 都是属于No 模式)

2、AUTOWIRE_BY_NAME = 1;(通过名字自动注入)

3、AUTOWIRE_BY_TYPE = 2;(通过类型自动注入)

4、AUTOWIRE_CONSTRUCTOR = 3;(通过构造函数自动注入)

5、AUTOWIRE_AUTODETECT = 4;(已经被标注过时)

注入模型,可以理解为bean注入的一种配置,设置之后,这个bean的所有依赖,都使用这种模式注入,要想指定模式,需要实现ImportBeanDefinitionRegistrar这个接口,和上面mybatis用法一样。

留一个问题,如果既设置了模式,又设置了@AutoWired注解,最后以谁为准?

那么mapper信息,是在spring加载bean的那个环节注册进来的,因为是通过@Import({ AutoConfiguredMapperScannerRegistrar.class })这个注解引进来的,所以也是在spring解析@Import这个注解的时候执行的,那么这个@Import注解是什么时候解析的,是在解析配置类的时候,也就是@Configuration这个注解解析的时候,@Configuration注解是ConfigurationClassPostProcessor解析的。

 ConfigurationClassPostProcessor实现了 BeanDefinitionRegistryPostProcessor接口,而 BeanDefinitionRegistryPostProcessor 接口继承了BeanFactoryPostProcessor接口,所以这是一个BeanFactory的后置处理器,BeanFactory的后置处理器是在spring注册完bean信息之后,也就是前面说的spring加载bean的第二步,如果从spring启动类说起的话,整个链路是这样的:

org.springframework.context.support.AbstractApplicationContext#refresh --> org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors --> org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry -->org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions

在这个processConfigBeanDefinitions方法里有一行代码:

this.reader.loadBeanDefinitions(configClasses);

这一句里面继续调org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass --> org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsFromRegistrars

这个就是最终执行扫描mapper的方法了,看下:

  1. private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
  2. registrars.forEach((registrar, metadata) ->
  3. registrar.registerBeanDefinitions(metadata, this.registry));
  4. }

这里执行的就是org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar#registerBeanDefinitions这个方法了,这个方法的代码前面已经说了。

spring通过BeanFactory后置处理器的方式,来让第三方可以定制bean信息的注册。

2.3.2.@MapperScan注册

看下@MapperScan注解的定义。

这里也有一个@Import(MapperScannerRegistrar.class)注解,所以和@Mapper注解的机制是一样,执行时机也是一样的, MapperScannerRegistrar这个类同样是实现了ImportBeanDefinitionRegistrar接口,主要看下org.mybatis.spring.annotation.MapperScannerRegistrar#registerBeanDefinitions这个方法,这个方法的主要逻辑在私有的org.mybatis.spring.annotation.MapperScannerRegistrar#registerBeanDefinitions这个方法里。

  1. void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {
  2. ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
  3. // this check is needed in Spring 3.1
  4. Optional.ofNullable(resourceLoader).ifPresent(scanner::setResourceLoader);
  5. Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
  6. if (!Annotation.class.equals(annotationClass)) {
  7. scanner.setAnnotationClass(annotationClass);
  8. }
  9. Class<?> markerInterface = annoAttrs.getClass("markerInterface");
  10. if (!Class.class.equals(markerInterface)) {
  11. scanner.setMarkerInterface(markerInterface);
  12. }
  13. Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
  14. if (!BeanNameGenerator.class.equals(generatorClass)) {
  15. scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
  16. }
  17. Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
  18. if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
  19. scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
  20. }
  21. scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
  22. scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
  23. List<String> basePackages = new ArrayList<>();
  24. basePackages.addAll(
  25. Arrays.stream(annoAttrs.getStringArray("value"))
  26. .filter(StringUtils::hasText)
  27. .collect(Collectors.toList()));
  28. basePackages.addAll(
  29. Arrays.stream(annoAttrs.getStringArray("basePackages"))
  30. .filter(StringUtils::hasText)
  31. .collect(Collectors.toList()));
  32. basePackages.addAll(
  33. Arrays.stream(annoAttrs.getClassArray("basePackageClasses"))
  34. .map(ClassUtils::getPackageName)
  35. .collect(Collectors.toList()));
  36. scanner.registerFilters();
  37. scanner.doScan(StringUtils.toStringArray(basePackages));
  38. }

主要就是给扫描器设置一些信息,主要看下:

    scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
    scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));

这两个信息,我们如下使用@MapperScan注解时,

@MapperScan(basePackages = {"com.dao.mapper"},sqlSessionTemplateRef = "db1sqlSessionTemplate",sqlSessionFactoryRef = "db1SqlSessionFactory")

sqlSessionTemplate和sqlSessionFactory会被设置成我们给的值,后续会把这两个值注入到mapper中,一般用于多数据源的情况。后续扫描器的处理就和@Mapper的注解处理是同一个方法了org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions。

2.3.3.小结

@Mapper的注册实现类:org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar

@MapperScan的注册实现类:org.mybatis.spring.annotation.MapperScannerRegistrar

这两个实现类都是属于mybatis的,通过实现spring的ImportBeanDefinitionRegistrar的接口(当然还实现了其他接口),与spring整合到一起,融入到spring的管理体系中,这也是spring的精髓所在,spring本身支持在bean生命周期的各个阶段进行扩展和自定义。

2.4.mapper的创建

通过前一阶段,所有的mapper信息都已经注册到了容器,下一步,就是根据注册的信息实例化mapper了。也就是执行spring加载bean流程的org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization这个方法,这个方法会调org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons这个方法,对容器中的所有单例的bean进行创建,创建的bean分两种情况,一种是FactoryBean类型的bean,一种是普通bean,因为mapper的代理类MapperFactoryBean实现了FactoryBean接口,所以是FactoryBean类型的bean,核心源码如下:

  1. // Trigger initialization of all non-lazy singleton beans...
  2. for (String beanName : beanNames) {
  3. RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
  4. if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
  5. if (isFactoryBean(beanName)) {
  6. // getBean(beanName)获取的是FactoryBean创建的bean实例
  7. // getBean("&"+beanName)获取FactoryBean本身,所以这里获取的是FactoryBean本身
  8. Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
  9. if (bean instanceof FactoryBean) {
  10. final FactoryBean<?> factory = (FactoryBean<?>) bean;
  11. boolean isEagerInit;
  12. // 判断是否有代码运行权限
  13. if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
  14. isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
  15. ((SmartFactoryBean<?>) factory)::isEagerInit,
  16. getAccessControlContext());
  17. }
  18. else {
  19. // 默认情况 FactoryBean 延迟创建bean
  20. // 但如果是 SmartFactoryBean,而且设置其 eagerInit 值为 true
  21. // 那么就进行提前创建
  22. isEagerInit = (factory instanceof SmartFactoryBean &&
  23. ((SmartFactoryBean<?>) factory).isEagerInit());
  24. }
  25. if (isEagerInit) {
  26. // 进行提前创建
  27. getBean(beanName);
  28. }
  29. }
  30. }
  31. else {
  32. getBean(beanName);
  33. }
  34. }
  35. }

如果是mapper的话,根据代码逻辑,这里不会提前创建,但是FactoryBean本身是会创建的,也就是mapper的代理类MapperFactoryBean是会创建的。创建逻辑就在org.springframework.beans.factory.support.AbstractBeanFactory#getBean这个方法里,最终一路下去会调到org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean,这个是创建bean的核心逻辑,代码比较多,和我们有关就是这一段:

  1. // Create bean instance.
  2. if (mbd.isSingleton()) {
  3. sharedInstance = getSingleton(beanName, () -> {
  4. try {
  5. return createBean(beanName, mbd, args);
  6. }
  7. catch (BeansException ex) {
  8. // Explicitly remove instance from singleton cache: It might have been put there
  9. // eagerly by the creation process, to allow for circular reference resolution.
  10. // Also remove any beans that received a temporary reference to the bean.
  11. destroySingleton(beanName);
  12. throw ex;
  13. }
  14. });
  15. bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
  16. }

最核心的是createBean(beanName, mbd, args)是这个方法,bean的实例化、属性填充和初始化等等都是在这里处理的。

实例化:通过有参的构造函数实例化MapperFactoryBean,参数就是mapper接口

属性填充:就是将依赖注入,这里会将sqlSessionTemplate和sqlSessionFactory注入,@MapperScan的情况下,指定了名称,会根据名称注入,默认自动配置的情况下,会根据类型注入

初始化:MapperFactoryBean有自己的逻辑,下面详细说下

看下MapperFactoryBean的继承体系。

 MapperFactoryBean的父类DaoSupport实现了InitializingBean接口,那么在MapperFactoryBean创建后会,在初始化阶段会调用org.springframework.dao.support.DaoSupport#afterPropertiesSet这个方法。

  1. public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
  2. this.checkDaoConfig();
  3. try {
  4. this.initDao();
  5. } catch (Exception var2) {
  6. throw new BeanInitializationException("Initialization of DAO failed", var2);
  7. }
  8. }

checkDaoConfig()是一个抽象方法,MapperFactoryBean做了实现:

  1. @Override
  2. protected void checkDaoConfig() {
  3. //检查sqlSessionTemplate不能为空,这里肯定不会为空,在属性填充阶段已经注入了
  4. super.checkDaoConfig();
  5. //检查mapper接口不能为空,这里肯定不会为空,在实例化阶段已经通过有参构造函数传进来了
  6. notNull(this.mapperInterface, "Property 'mapperInterface' is required");
  7. Configuration configuration = getSqlSession().getConfiguration();
  8. if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
  9. try {
  10. configuration.addMapper(this.mapperInterface);
  11. } catch (Exception e) {
  12. logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
  13. throw new IllegalArgumentException(e);
  14. } finally {
  15. ErrorContext.instance().reset();
  16. }
  17. }
  18. }

主要看下configuration.addMapper(this.mapperInterface)这个方法:

  1. public <T> void addMapper(Class<T> type) {
  2. if (type.isInterface()) {
  3. if (hasMapper(type)) {
  4. throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
  5. }
  6. boolean loadCompleted = false;
  7. try {
  8. //将mapper接口包装成MapperProxyFactory对象保存至map缓存起来,后面mapper注入service时,会从这里获取
  9. knownMappers.put(type, new MapperProxyFactory<>(type));
  10. // It's important that the type is added before the parser is run
  11. // otherwise the binding may automatically be attempted by the
  12. // mapper parser. If the type is already known, it won't try.
  13. MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
  14. parser.parse();
  15. loadCompleted = true;
  16. } finally {
  17. if (!loadCompleted) {
  18. knownMappers.remove(type);
  19. }
  20. }
  21. }
  22. }

再继续看parser.parse()这个方法:

  1. public void parse() {
  2. String resource = type.toString();
  3. if (!configuration.isResourceLoaded(resource)) {
  4. // 先加载xml资源,如:UserMapper.xml,parse方法先解析配置文件,然后再解析接口里的注解配置,且注解里的配置会覆盖配置文件里的配置,也就是说注解的优先级高于配置文件
  5. loadXmlResource();
  6. // 添加解析过的映射,下次判断有就不在解析
  7. configuration.addLoadedResource(resource);
  8. // 命名空间
  9. assistant.setCurrentNamespace(type.getName());
  10. // 二级缓存的处理,处理注解@CacheNamespace与@CacheNamespaceRef
  11. parseCache();
  12. parseCacheRef();
  13. //获取mapper的所有方法
  14. Method[] methods = type.getMethods();
  15. for (Method method : methods) {
  16. try {
  17. // issue #237
  18. if (!method.isBridge()) {
  19. // 解析方法,对方法上各种注解的解析,并把解析完的信息,添加到Configuration的mappedStatements中去,在后面执行sql语句时根据方法名取出来调用
  20. parseStatement(method);
  21. }
  22. } catch (IncompleteElementException e) {
  23. configuration.addIncompleteMethod(new MethodResolver(this, method));
  24. }
  25. }
  26. }
  27. parsePendingMethods();
  28. }

至此,MapperFactoryBean的初始化也完事了。回到org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean这个方法,在这个方法里的创建单例bean后,会调用org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#getObjectForBeanInstance,接着调用org.springframework.beans.factory.support.AbstractBeanFactory#getObjectForBeanInstance,这个方法会决定返回MapperFactoryBean本身还是它创建的bean。

  1. protected Object getObjectForBeanInstance(
  2. Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {
  3. // Don't let calling code try to dereference the factory if the bean isn't a factory.
  4. if (BeanFactoryUtils.isFactoryDereference(name)) {//是否是FactoryBean名字的前缀&
  5. if (beanInstance instanceof NullBean) {
  6. return beanInstance;
  7. }
  8. if (!(beanInstance instanceof FactoryBean)) {//不是FactoryBean的话名字有&会报异常
  9. throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass());
  10. }
  11. if (mbd != null) {
  12. mbd.isFactoryBean = true;
  13. }
  14. //要找的就是FactoryBean自身,直接返回
  15. return beanInstance;
  16. }
  17. // Now we have the bean instance, which may be a normal bean or a FactoryBean.
  18. // If it's a FactoryBean, we use it to create a bean instance, unless the
  19. // caller actually wants a reference to the factory.
  20. //不是FactoryBean就直接返回,也就是普通的bean
  21. if (!(beanInstance instanceof FactoryBean)) {
  22. return beanInstance;
  23. }
  24. //除了以上逻辑,那就剩下最后一种情况了,就是获取的是FactoryBean创建的bean
  25. Object object = null;
  26. if (mbd != null) {
  27. mbd.isFactoryBean = true;
  28. }
  29. else {
  30. //从FactoryBean的缓存中获取
  31. object = getCachedObjectForFactoryBean(beanName);
  32. }
  33. if (object == null) {
  34. // Return bean instance from factory.
  35. FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
  36. // Caches object obtained from FactoryBean if it is a singleton.
  37. //mbd没定义,但是FactoryBean是有定义的,获取mbd
  38. if (mbd == null && containsBeanDefinition(beanName)) {
  39. mbd = getMergedLocalBeanDefinition(beanName);
  40. }
  41. boolean synthetic = (mbd != null && mbd.isSynthetic());
  42. object = getObjectFromFactoryBean(factory, beanName, !synthetic);
  43. }
  44. return object;
  45. }

第一段是处理FactoryBean的情况,也就是创建MapperFactoryBean走的逻辑,中间是普通bean的情况,最后一段是获取FactoryBean创建的bean,也就是MapperFactoryBean创建的bean,这个是在需要注入mapper时触发的,下一节说。

2.5.mapper的注入

一般在sercie里使用mapper:

    @Autowired
    private MyMapper myMapper;

当创建service时,在属性填充阶段会去获取依赖的mapper进行注入,也就是org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean这个方法里:

  1. // Initialize the bean instance.
  2. Object exposedObject = bean;
  3. try {
  4. populateBean(beanName, mbd, instanceWrapper);
  5. exposedObject = initializeBean(beanName, exposedObject, mbd);
  6. }
  7. catch (Throwable ex) {
  8. if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
  9. throw (BeanCreationException) ex;
  10. }
  11. else {
  12. throw new BeanCreationException(
  13. mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
  14. }
  15. }

以上截取了这个方法的中间代码段,主要就是在创建完bean后,对bean进行属性填充和初始化,如果当前的bean是依赖mapper的service,那么mapper就是在这个时候通过属性填充这个方法注入进来的,具体方法就是org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean这个方法,逻辑并不复杂,如果属性是bean,最终还是调org.springframework.beans.factory.support.AbstractBeanFactory#getBean方法获取bean,也就是获取mapper,在上一节创建bean的时候,最后一段逻辑是获取FactoryBean创建的bean,也就是mapper的获取属于这种方式,最终调的是object = getObjectFromFactoryBean(factory, beanName, !synthetic);这个方法获取的bean,里面接着调org.springframework.beans.factory.support.FactoryBeanRegistrySupport#doGetObjectFromFactoryBean这个方法,这个方法有这样一句代码:

object = AccessController.doPrivileged((PrivilegedExceptionAction<Object>) factory::getObject, acc);

主要是factory::getObject这个方法,这里的factory就是当前mapper的MapperFactoryBean,也就是调MapperFactoryBean的getObject的方法获取。

链路如下:

org.mybatis.spring.mapper.MapperFactoryBean#getObject -->

org.mybatis.spring.SqlSessionTemplate#getMapper -->

org.apache.ibatis.session.Configuration#getMapper -->

org.apache.ibatis.binding.MapperRegistry#getMapper

看下这个方法的代码:

  1. public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  2. final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  3. if (mapperProxyFactory == null) {
  4. throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  5. }
  6. try {
  7. return mapperProxyFactory.newInstance(sqlSession);
  8. } catch (Exception e) {
  9. throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  10. }
  11. }

从knownMappers里面获取MapperProxyFactory,这个是前面创建MapperFactoryBean时设置的,所以这里是一定存在的,不存在会抛出异常。然后调用org.apache.ibatis.binding.MapperProxyFactory#newInstance的这个方法,

  1. protected T newInstance(MapperProxy<T> mapperProxy) {
  2. return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  3. }
  4. public T newInstance(SqlSession sqlSession) {
  5. final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  6. return newInstance(mapperProxy);
  7. }

这里调的是newInstance(SqlSession sqlSession)这个方法,内部又调了newInstance(MapperProxy<T> mapperProxy)这个方法,这个方法就是通过JDK的动态代理生成代理对象,也就是最终的代理对象是MapperProxy,所以最终注入的也是MapperProxy这个代理对象,mapper有了后,就可以执行方法了。

2.5.mapper执行方法

有了mapper对象后,就可以执行方法了,像这样:

Food food = myMapper.getById(7L);

因为这里的myMapper是一个代理对象,也就是MapperProxy,当执行getById方法的时候,其实执行的是代理对象的org.apache.ibatis.binding.MapperProxy#invoke方法。

  1. @Override
  2. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  3. try {
  4. if (Object.class.equals(method.getDeclaringClass())) {
  5. return method.invoke(this, args);
  6. } else if (isDefaultMethod(method)) {
  7. return invokeDefaultMethod(proxy, method, args);
  8. }
  9. } catch (Throwable t) {
  10. throw ExceptionUtil.unwrapThrowable(t);
  11. }
  12. final MapperMethod mapperMethod = cachedMapperMethod(method);
  13. return mapperMethod.execute(sqlSession, args);
  14. }

最终执行的mapperMethod.execute(sqlSession, args)这个方法,其中mapperMethod的也就是方法上的或者配置文件中的sql语句及相关注解之类的信息,这些信息在MapperFactoryBean初始化阶段已经解析好了,都放在了org.apache.ibatis.session.Configuration里了,这里可以直接拿来使用,最终执行mapperMethod.execute(sqlSession, args)这个方法:

  1. public Object execute(SqlSession sqlSession, Object[] args) {
  2. Object result;
  3. switch (command.getType()) {
  4. case INSERT: {
  5. Object param = method.convertArgsToSqlCommandParam(args);
  6. result = rowCountResult(sqlSession.insert(command.getName(), param));
  7. break;
  8. }
  9. case UPDATE: {
  10. Object param = method.convertArgsToSqlCommandParam(args);
  11. result = rowCountResult(sqlSession.update(command.getName(), param));
  12. break;
  13. }
  14. case DELETE: {
  15. Object param = method.convertArgsToSqlCommandParam(args);
  16. result = rowCountResult(sqlSession.delete(command.getName(), param));
  17. break;
  18. }
  19. case SELECT:
  20. if (method.returnsVoid() && method.hasResultHandler()) {
  21. executeWithResultHandler(sqlSession, args);
  22. result = null;
  23. } else if (method.returnsMany()) {
  24. result = executeForMany(sqlSession, args);
  25. } else if (method.returnsMap()) {
  26. result = executeForMap(sqlSession, args);
  27. } else if (method.returnsCursor()) {
  28. result = executeForCursor(sqlSession, args);
  29. } else {
  30. Object param = method.convertArgsToSqlCommandParam(args);
  31. result = sqlSession.selectOne(command.getName(), param);
  32. if (method.returnsOptional() &&
  33. (result == null || !method.getReturnType().equals(result.getClass()))) {
  34. result = Optional.ofNullable(result);
  35. }
  36. }
  37. break;
  38. case FLUSH:
  39. result = sqlSession.flushStatements();
  40. break;
  41. default:
  42. throw new BindingException("Unknown execution method for: " + command.getName());
  43. }
  44. if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
  45. throw new BindingException("Mapper method '" + command.getName()
  46. + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
  47. }
  48. return result;
  49. }

这个方法就是具体执行数据库的操作了,逻辑也并不难,拿查询说吧,查询单条的会执行

result = sqlSession.selectOne(command.getName(), param);

这一句,这里的sqlSession就是SqlSessionTemplate,我们知道,在创建SqlSessionTemplate时,所有的方法执行,也是代理执行的,上面这句,最终会执行到:

  1. @Override
  2. public <T> T selectOne(String statement, Object parameter) {
  3. return this.sqlSessionProxy.selectOne(statement, parameter);
  4. }

这个在说SqlSessionTemplate的时候,也提到这一点了,再往里就是具体数据库操作了,这里就点到为止了。

3.总结

上面啰嗦了这么多,主要就是从spring的角度来看看,mybatis是如何融入进来的,现在就来做个架构的总结吧。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/498166
推荐阅读
相关标签
  

闽ICP备14008679号