• springboot整合mybatis


    1.pom

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

    引入一个这个注解,就有了mybatis的强大功能,神奇
    mybaits使用流程:

    • 引入依赖
    • 配置数据库信息
    • 创建数据库对应的实体类
    • 创建mapper接口,定义查询方法 @Mapper
    • 创建mapper对应的xml,编写sql语句
    • 创建service,调用mapper接口的方法 @Service
    • 创建controller,调用service的方法 @Controller

    2.自动配置

    引入 mybaits starter会引入这个autoconfigure
    在这里插入图片描述
    MybatisAutoConfiguration引入,项目启动时会扫描到对应的autoconfigure下的spring.factories文件中的配置。
    在这里插入图片描述
    引入mybaits-starter会自动引入的jar包:
    在这里插入图片描述
    mybaits自动配置类
    在这里插入图片描述

    3.扫描mapper接口

    refresh的invokeBeanFactoryPostProcessors(beanFactory);

    也就是扫描到我们定义的mapper接口,变成BeanDefinition。
    这里是两个问题:
    1.接口默认不会被spring装载成BeanDefinition。所以mybatis框架要处理。
    2.接口不会被实例化,需要对接口类型进行封装。

    3.1.mybatis-1.1.1-全局配置方式

    在启动类加注解:@MapperScan(“xxx.xxx.xxx.mapper”)
    作用就是会扫描到配置的包下面的所有mapper接口,扫描这些接口的目的是为了创建代理对象,因为接口不能实例化,需要用代理方式执行接口中的方法。这里用的是JDK动态代理。

    这个注解能生效是因为引入了MapperScannerRegistrar类
    mapper啥啥啥输入注册,应该就是这个意思
    在这里插入图片描述
    MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,当执行refresh的invokeBeanFactoryPostProcessors会加载所有实现了ImportBeanDefinitionRegistrar接口的类,并执行它的registerBeanDefinitions方法,即执行MapperScannerRegistrar.registerBeanDefinitions

    在invokeBeanFactoryPostProcessors方法里会执行两个接口,按先后执行顺序为:

    第一个是BeanDefinitionRegistryPostProcessor;(主要 功能是注册beanDefinition)
    第二个是BeanFactoryPostProcessor。

    具体流程:
    1.AbstractApplicationContext类的refresh方法的invokeBeanFactoryPostProcessors方法在这里插入图片描述
    2.PostProcessorRegistrationDelegate类的invokeBeanFactoryPostProcessors方法

    • invokeBeanFactoryPostProcessors方法会执行三次 第一次扫描conpnent注解的bean,第二次默认情况下不执行,第三次扫描mapper接口
      在这里插入图片描述
      3.PostProcessorRegistrationDelegate类的invokeBeanDefinitionRegistryPostProcessors方法
      在这里插入图片描述
      4.ConfigurationClassPostProcessor类的postProcessBeanDefinitionRegistry方法
      在这里插入图片描述
      5.ConfigurationClassPostProcessor类的processConfigBeanDefinitions方法
      在这里插入图片描述
      6.ConfigurationClassBeanDefinitionReader类的loadBeanDefinitions方法
      在这里插入图片描述
      这里configurationmodel有很多,关键就是这个启动类,在启动类加了mapperScan注解,他引入了MapperScannerRegistrar。
      在这里插入图片描述

    7.ConfigurationClassBeanDefinitionReader类的loadBeanDefinitionsForConfigurationClass方法
    这里是关键,当遍历到MapperScannerRegistrar时,会执行他的registerBeanDefinitions方法,因为他实现了ImportBeanDefinitionRegistrar接口。这里的传参就是configClass的ImportBeanDefinitionRegistrars属性,这个集合里有当前类中所有实现了ImportBeanDefinitionRegistrar接口的类

    • 如果既加了mapperscan又加了@mapper 则执行@mapper时 下面的第一个if会进去,然后直接返回。也就是mapperscan优先于@mapper,会先生效。
      在这里插入图片描述

    8.ConfigurationClassBeanDefinitionReader类的loadBeanDefinitionsFromRegistrars方法
    遍历所有ImportBeanDefinitionRegistrars,调用他们的registerBeanDefinitions方法
    在这里插入图片描述
    9.MapperScannerRegistrar.java类的registerBeanDefinitions方法
    就是这个MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口。
    在这里插入图片描述
    10.ClassPathMapperScanner类的doScan方法
    会调用父类ClassPathBeanDefinitionScanner的doScan方法
    在这里插入图片描述
    11.ClassPathBeanDefinitionScanner类的doScan方法
    在这里插入图片描述
    12.ClassPathBeanDefinitionScanner类的registerBeanDefinition方法
    在这里插入图片描述
    13.BeanDefinitionReaderUtils类的registerBeanDefinition方法
    在这里插入图片描述
    14.最后是DefaultListableBeanFactory类的registerBeanDefinition方法。

    扫描包路径下的所有mapper
    在这里插入图片描述
    一般情况下,**mapper是接口类。默认情况下,spring对接口interface是不会生成BeanDefinition 对象。在mybatis里,为了生成BeanDefinition 对象,则重写了isCandidateComponent方法,上面箭头指的方法就会调用,是接口类型也要生成BeanDefinition,所以这里service接口也会返回 **。
    这里解决了标题3的第一个问题。
    在这里插入图片描述

    通过这个方法就可以生成我们自己的mapper接口的beandefinition了。

    spring是扫描所有基于@Component注解的类,并生成bean定义,然后放到bean定义注册表中。而我们这里的mapper没有加@Component注解,所以在执行上述逻辑之前,实际的bean定义注册表中是没有这些mapper的bean定义的。执行上述逻辑之后,才在bean定义注册表中加入了这些mapper的beanDefinition

    补充上面的第十步
    将beanDefinitions放到beanFactory之后,还有一步重要的操作,就是执行本类ClassPathBeanDefinitionScanner中的processBeanDefinitions
    在这里插入图片描述
    这里是把每个mapper的Bean定义的BeanClass设置为mapperFactoryBeanClass,这样做是为了让后面创建bean时,可以使用MapperFactoryBean来创建bean。
    在这里插入图片描述
    在这里插入图片描述
    保存了自定义mapper的信息,并将类型变为mapperFactoryBeanClass。这样动态代理的时候就可以找到我们自定义的mapper接口了。
    在这里插入图片描述

    为什么要把mapper的BeanClass设置为mapperFactoryBeanClass?

    因为mapper是接口,但是接口是不能实例化的,所以mybatis中就把mapper的beanDefinition的beanClass定义为mapperFactoryBean类型,它的父类SqlSessionDaoSupport的父类DaoSupport实现了InitializingBean接口,所以spring在实例化对象时会调用对应的afterPropertiesSet方法,作用是添加mapper到configuration(一般不会用到,在初期解析mapper.xml时就已经加了),实现了FactoryBean接口,所以后续会调用getObject方法,这个方法就是创建mapper的代理对象的,也就是相当于实例化了mapper
    这里解决了标题3的第二个问题。

    3.2.mybatis-1.1.1-单接口配置方式

    @mapper
    这样需要在每一个mapper接口添加此注解
    这个注解是怎么生效的呢
    在这里插入图片描述
    加载MybatisAutoConfiguration类后,因为MybatisAutoConfiguration内部类AutoConfiguredMapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,当执行refresh的invokeBeanFactoryPostProcessors会加载所有实现了ImportBeanDefinitionRegistrar接口的类,并执行它的registerBeanDefinitions方法,即执行AutoConfiguredMapperScannerRegistrar.registerBeanDefinitions。这个概念和全局配置那种方式是一样的。
    在这里插入图片描述
    注:AutoConfiguredMapperScannerRegistrar的注入受mapperscan注解的影响,如果有mapperscan注解,则不会注入AutoConfiguredMapperScannerRegistrar类
    MybatisAutoConfiguration类
    在这里插入图片描述

    到这就已经把mapper接口的定义信息存到beanFactory了,等待实例化。
    具体流程
    1.refresh方法
    在这里插入图片描述
    2.AbstractApplicationContext类的invokeBeanFactoryPostProcessors方法
    在这里插入图片描述
    3.PostProcessorRegistrationDelegate类的invokeBeanFactoryPostProcessors方法
    在这里插入图片描述
    在这里插入图片描述
    4.ConfigurationClassPostProcessor类的postProcessBeanDefinitionRegistry方法
    在这里插入图片描述
    .5.ConfigurationClassPostProcessor类的processConfigBeanDefinitions方法
    在这里插入图片描述
    6.ConfigurationClassBeanDefinitionReader类的loadBeanDefinitions
    在这里插入图片描述
    7.ConfigurationClassBeanDefinitionReader类的loadBeanDefinitionsForConfigurationClass方法

    在这里插入图片描述
    8.ConfigurationClassBeanDefinitionReader类的loadBeanDefinitionsFromRegistrars方法
    在这里插入图片描述
    9.MybatisAutoConfiguration类的内部类AutoConfiguredMapperScannerRegistrar的registerBeanDefinitions方法
    就是这个内部类实现了ImportBeanDefinitionRegistrar接口
    在这里插入图片描述
    10.ClassPathMapperScanner的dosacn方法
    在这里插入图片描述
    11.ClassPathBeanDefinitionScanner类的doScan方法
    在这里插入图片描述
    在这里插入图片描述
    12.BeanDefinitionReaderUtils类的registerBeanDefinition方法
    在这里插入图片描述
    会调用DefaultListableBeanFactory类的registerBeanDefinition,完成beandefinition的注册
    在这里插入图片描述
    hasBeanCreationStarted方法
    在这里插入图片描述
    注:上面mybatis-spring-boot-starter版本为1.1.1版本,mybatis-spring-boot-starter 2.2.2版本的mapperscan和mapper注解则是都实例化一个MapperScannerConfigurer类,它实现了BeanDefinitionRegistryPostProcessor接口(和ConfigurationClassPostProcessor一样),容器启动时会调用该类的postProcessBeanDefinitionRegistry方法,然后调用ClassPathBeanDefinitionScanner的scan方法,然后调用子类ClassPathMapperScanner的doscan方法把所有的maper变成beandefinition并且通过processBeanDefinitions方法把所有beandefinition变成MapperFactoryBean类型。注入前会通过registerFilters方法判断是mapperscan还是mapper。
    注册MapperScannerConfigurer类型
    PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors()会执行三次invokeBeanDefinitionRegistryPostProcessors方法,第一次是执行ConfigurationClassPostProcessor,把自定义的bean变成beanDefinition,还有自动配置类,这时如果加了mapperScan,则会在mapperScan引入的MapperScannerRegistrar类中注册MapperScannerConfigurer,如果没加mapperScan,则会在mybaitsAutoConfiuration类的内部类AutoConfiguredMapperScannerRegistrar中注册MapperScannerConfigurer。然后在第三次执行invokeBeanDefinitionRegistryPostProcessors时,通过MapperScannerConfigurer,扫描到所有mapper,变成beanDefinition,把所有beandefinition变成MapperFactoryBean类型。

    3.3.mybatis-2.2.2

    3.3.1.加mapperScan

    在这里插入图片描述
    加载每个配置类(第一个invokeBeanDefinitionRegistryPostProcessors方法)
    当扫描到主启动类时,会扫描到启动类加的mapperScan注解引入的MapperScannerRegistrar
    在这里插入图片描述
    处理实现了ImportBeanDefinitionRegistrar接口的类,也就是MapperScannerRegistrar
    在这里插入图片描述
    然后会执行MapperScannerRegistrar的registerBeanDefinitions方法
    在这里插入图片描述
    然后执行重载的registerBeanDefinitions方法
    在这里插入图片描述
    然后注册beanDefinition 类型是MapperScannerConfigurer 名称是MapperScannerRegistrar
    在这里插入图片描述

    3.3.2.不加mapperScan

    MybatisAutoConfiguration的注册也是在第一个invokeBeanDefinitionRegistryPostProcessors方法
    不加注解的话则会走MybatisAutoConfiguration类的AutoConfiguredMapperScannerRegistrar类,因为AutoConfiguredMapperScannerRegistrar也实现了ImportBeanDefinitionRegistrar接口,所以也会走他的registerBeanDefinitions方法。
    在这里插入图片描述
    AutoConfiguredMapperScannerRegistrar类的registerBeanDefinitions方法
    在这里插入图片描述
    注册了MapperScannerConfigurer为beanDefinition。

    这个类能生效前提是没加mapperScan注解,如果加了就不会注入这个类

    在这里插入图片描述

    3.3.3.MapperScannerConfigurer扫描mapper变成beanDefinition

    加或者不加mapperScan逻辑相同
    PostProcessorRegistrationDelegate类的invokeBeanFactoryPostProcessors方法
    在这里插入图片描述
    这里能获取到MapperScannerConfigurer是因为它实现了BeanDefinitionRegistryPostProcessor接口
    在这里插入图片描述
    然后继续执行最终会调用ClassPathMapperScanner类的doscan方法
    在这里插入图片描述
    然后调用父类ClassPathBeanDefinitionScannerdoScan方法

    	protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    		Assert.notEmpty(basePackages, "At least one base package must be specified");
    		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
    		//这里basePackage 如果加了mapperScan注解并且指定了包路径就用指定的包路径
    		//如果没加mapperScan注解则是启动类所在的包路径
    		for (String basePackage : basePackages) {
    			//mybaits要单独处理 获取mapper接口
    			//这里如果加了mapperscan注解则不需要扫描mapper注解,否则会扫描mapper注解。
    			//所以 如果没加mapperscan 则必须要加mapper注解
    			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);
    				if (candidate instanceof AbstractBeanDefinition) {
    					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
    				}
    				if (candidate instanceof AnnotatedBeanDefinition) {
    					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
    				}
    				if (checkCandidate(beanName, candidate)) {
    					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
    					definitionHolder =
    							AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
    					beanDefinitions.add(definitionHolder);
    					//注册beandefinition
    					registerBeanDefinition(definitionHolder, this.registry);
    				}
    			}
    		}
    		return beanDefinitions;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    3.3.4.整体流程(重要)

    1.先注册MapperScannerConfigurer,它实现了BeanDefinitionRegistryPostProcessor接口。这个过程是在PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors方法第一次执行,
    方法调用顺序为:
    AbstractApplicationContext#refresh方法的invokeBeanFactoryPostProcessors(beanFactory)方法 ->
    PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors方法中的invokeBeanDefinitionRegistryPostProcessors方法 -> ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry方法中的processConfigBeanDefinitions方法 -> parser.parse(candidates)和this.reader.loadBeanDefinitions(configClasses)方法。

    这两个关键方法很关键。
    parser.parse(candidates)负责处理compnentscan controller等。关键的一点是会把实现了ImportBeanDefinitionRegistrar接口的bean放到ConfigurationClass属性的importBeanDefinitionRegistrars属性中。这其中就包括@mapperscan注解引入的MapperScannerRegistrar
    然后ConfigurationClassBeanDefinitionReader类的loadBeanDefinitions方法则会处理实现了ImportBeanDefinitionRegistrar接口的bean,调用他们的registerBeanDefinitions方法,这是方式之一,方式之二就是没加@mapperscan注解的话,由mybatis自动配置类实现,他会引入另一个实现了ImportBeanDefinitionRegistrar接口的bean,也就是AutoConfiguredMapperScannerRegistrar,然后引入MapperScannerConfigurer的过程就和方式1相同了。

    关键的MapperScannerConfigurer就是这么来的。

    然后就是MapperScannerConfigurer干活的时候了,这个过程是在PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors方法第三次执行,能执行是因为他实现了BeanDefinitionRegistryPostProcessor接口,就像把他扫描到的ConfigurationClassPostProcessor一样,但是它干的活也就到这了,后面是交给了
    ClassPathBeanDefinitionScanner及他的子类ClassPathMapperScanner,方法是ClassPathBeanDefinitionScanner#doScanClassPathMapperScanner由mybatis提供,重写isCandidateComponent方法保证mapper接口也可以被spring扫描到。
    doScan方法针对加没加@mapperscan也有两种逻辑,加了的话会扫描到所有mapper,没加的话在自动配置类(MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar类的registerBeanDefinitions方法中)指明@mapper注解也要被扫描到。

    再总结一下,先注册MapperScannerConfigurer(通过自动配置或mapperscan注解),然后通过MapperScannerConfigurer,实现mapper的扫描,具体工作的bean为ClassPathBeanDefinitionScanner及他的子类ClassPathMapperScanner

    4.实例化

    接下来,执行到refresh.finishBeanFactoryInitialization(beanFactory),在这里将会把bean定义注册表中的所有的beanDefinition进行实例化,然后放到bean缓存池中,供应用程序调用。

    注意这里是把bean定义注册表中的所有beanDefinition遍历处理挨个进行实例化,那么mapper、SqlSessionFactory、SqlSessionTemplate实例化的先后顺序是怎样的呢? 这里需注意一点,在每个创建bean实例之后,初始化bean之前,会执行populateBean进行属性赋值,如果依赖其他的bean,那么会先创建依赖的bean,所以,即使先实例化的是controller(正常controller依赖的service,service依赖mapper,mapper依赖sqlSessionTemplate,sqlSessionTemplate依赖SqlSessionFactory,但实例化的顺序是反过来的),最终还是先实例化SqlSessionFactory。

    在这里插入图片描述
    DataSource自动初始化就是配置数据源,默认hikari,springboot会引入spring-boot-starter-jdbc,这里会引入hikari,所以不配置的话默认使用hikari数据源。

    4.1.SqlSessionFactory

    在加载mybatis自动配置类时,会先判断SqlSessionFactory(mybatis jar包中)类和SqlSessionFactoryBean(mybatis-spring jar包中)类是否引入了,这里肯定是引入了(在mybatis-starter中),这里是为了注入bean,只是扫描到了SqlSessionFactory类,但是并没有实例化,所以会在这里进行实例化。
    在这里插入图片描述
    传入的是datasource,这个应该是在配置文件配置好的,很重要,保存数据库的连接信息,会被封装到SqlSessionFactory唯一的一个属性Configuration属性的Environment属性中。
    最后会调用SqlSessionFactoryBean的getObject,构建SqlSessionFactory,返回SqlSessionFactoryBean的SqlSessionFactory属性。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    继续调用buildSqlSessionFactory(),主要就是填充前面创建的configuration对象(在MybatisAutoConfiguration类的sqlSessionFactory方法中创建的)。

    在这里插入图片描述

    在这里插入图片描述
    这个configuration里有很多属性,
    在这里插入图片描述
    在这里插入图片描述
    对应的xml
    在这里插入图片描述
    buildSqlSessionFactory就是通过XMLConfigBuilder解析mybatis配置,通过XMLMapperBuilder解析mapper.xml的配置(重点是生成mappedStatements、resultMaps、sqlFragments等),以及其他的配置,最终放到Configuration里,供后面使用。
    在XMLMapperBuilder.parse()里通过mapperRegistry.addMapper方法,会把mapper接口添加到knownMappers中(knownMappers结构为HashMap,每个mapper的value为MapperProxyFactory对象),那么后面调用getMapper的时候就可以直接获取了。所以XMLMapperBuilder.parse()方法,完成了mapper接口和mapperproxyfactory这个mapper代理工厂的绑定。(获取是为了实例化mapper接口,也就是创建mapper代理对象
    这里进行绑定的前提是在resources/mapper/**下面要有*mapper.xml文件,因为mybaitsPlus可能不会写mapper.xml,所以如果没写的话会在实例化mapper的时候进行绑定。afterPropertiesSet方法(InitializingBean接口),

    • configuration创建
      在这里插入图片描述
      在这里插入图片描述
      factory.setConfiguration(configuration)就是把构建的SqlSessionFactoryBean.class赋值给SqlSessionFactoryBean的configuration属性。后面buildSqlSessionFactory会用到。
      yml配置
      在这里插入图片描述
      自定义配置类
      在这里插入图片描述
    • buildSqlSessionFactory方法
      protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
    
        final Configuration targetConfiguration;
    
        XMLConfigBuilder xmlConfigBuilder = null;
        if (this.configuration != null) {
          //就是上面创建的configuration对象,继续完善这个对象
          targetConfiguration = this.configuration;
          if (targetConfiguration.getVariables() == null) {
            targetConfiguration.setVariables(this.configurationProperties);
          } else if (this.configurationProperties != null) {
            targetConfiguration.getVariables().putAll(this.configurationProperties);
          }
        } else if (this.configLocation != null) {
          xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
          targetConfiguration = xmlConfigBuilder.getConfiguration();
        } else {
          LOGGER.debug(
              () -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
          targetConfiguration = new Configuration();
          Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
        }
    
        Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
        Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
        Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
    	//指定Mybatis的实体目录
        if (hasLength(this.typeAliasesPackage)) {
          scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
              .filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
              .filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
        }
    
        if (!isEmpty(this.typeAliases)) {
          Stream.of(this.typeAliases).forEach(typeAlias -> {
            targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
            LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
          });
        }
    
        if (!isEmpty(this.plugins)) {
          Stream.of(this.plugins).forEach(plugin -> {
            targetConfiguration.addInterceptor(plugin);
            LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
          });
        }
    
        if (hasLength(this.typeHandlersPackage)) {
          scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
              .filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
              .forEach(targetConfiguration.getTypeHandlerRegistry()::register);
        }
    
        if (!isEmpty(this.typeHandlers)) {
          Stream.of(this.typeHandlers).forEach(typeHandler -> {
            targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
            LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
          });
        }
    
        targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler);
    
        if (!isEmpty(this.scriptingLanguageDrivers)) {
          Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
            targetConfiguration.getLanguageRegistry().register(languageDriver);
            LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");
          });
        }
        Optional.ofNullable(this.defaultScriptingLanguageDriver)
            .ifPresent(targetConfiguration::setDefaultScriptingLanguage);
    
        if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls
          try {
            targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
          } catch (SQLException e) {
            throw new NestedIOException("Failed getting a databaseId", e);
          }
        }
    
        Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);
    
        if (xmlConfigBuilder != null) {
          try {
            xmlConfigBuilder.parse();
            LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
          } catch (Exception ex) {
            throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
          } finally {
            ErrorContext.instance().reset();
          }
        }
    
        targetConfiguration.setEnvironment(new Environment(this.environment,
            this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
            this.dataSource));
    	//重要1 在这儿解析所有的mapper.xml文件
        if (this.mapperLocations != null) {
          if (this.mapperLocations.length == 0) {
            LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
          } else {
          	//遍历mapper.xml文件进行解析
            for (Resource mapperLocation : this.mapperLocations) {
              if (mapperLocation == null) {
                continue;
              }
              try {
               	//创建xml的构造器
                XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                    targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
                xmlMapperBuilder.parse();
              } catch (Exception e) {
                throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
              } finally {
                ErrorContext.instance().reset();
              }
              LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
            }
          }
        } else {
          LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
        }
    
        return this.sqlSessionFactoryBuilder.build(targetConfiguration);
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124

    重要1:
    在这里插入图片描述

    • parse方法
      public void parse() {
        if (!configuration.isResourceLoaded(resource)) {
          //1.解析mapper标签以及内部的标签
          configurationElement(parser.evalNode("/mapper"));
          configuration.addLoadedResource(resource);
          //2.构建mapper与dao的关系
          bindMapperForNamespace();
        }
    
        parsePendingResultMaps();
        parsePendingCacheRefs();
        parsePendingStatements();
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    解析mapper标签以及内部的标签

    • configurationElement方法
      private void configurationElement(XNode context) {
        try {
        	//获取namespace 也就是mapper接口
          String namespace = context.getStringAttribute("namespace");
          if (namespace == null || namespace.isEmpty()) {
            throw new BuilderException("Mapper's namespace cannot be empty");
          }
          builderAssistant.setCurrentNamespace(namespace);
          cacheRefElement(context.evalNode("cache-ref"));
          cacheElement(context.evalNode("cache"));
          //解析parameterMap标签
          parameterMapElement(context.evalNodes("/mapper/parameterMap"));
          //解析resultMap标签
          resultMapElements(context.evalNodes("/mapper/resultMap"));
          //解析sql标签
          sqlElement(context.evalNodes("/mapper/sql"));
          //解析定义的sql语句
          buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在这里插入图片描述
    list是mapper.xml中的sql语句
    在这里插入图片描述
    parseStatementNode方法做具体工作,就是解析标签上的所有的属性,然后封装成MappedStatement对象最终放到配置类Configuration的mappedStatements属性上。key 为 id ,mapper接口对应方法的全限定名,value 为 MappedStatement。
    执行完parse方法中的 configurationElement(parser.evalNode(“/mapper”))方法:
    在这里插入图片描述
    这里能获取到两个,早期是通过方法名方式进行查询的。现在是根据全限定名

    在这里插入图片描述
    构建mapper接口与代理工厂的映射关系

    • bindMapperForNamespace();
      在这里插入图片描述
      addmapper方法
      在这里插入图片描述

    在这里插入图片描述
    将mapper接口封装成MapperProxyFactory然后放到knownMappers属性中。
    key是mapper接口的全限定名称,value是对应的MapperProxyFactory,通过这个代理工厂去创建对应的mapper代理类。
    在这里插入图片描述
    构建SqlSessionFactory时会构建Configuration对象,这个对象维护在MybatisProperties类中,
    MybatisProperties关联MybatisAutoConfiguration自动配置类
    如果在配置文件配了相关属性 例如mybatis.configuration.map-underscore-to-camel-case=true
    则Configuration会在MybatisProperties中实例化,否则会在构建SqlSessionFactory时实例化,
    applyConfiguration方法中。

    4.2.sqlSessionTemplate

    创建sqlSessionTemplate需要上面创建的sqlSessionFactory
    在这里插入图片描述
    通过动态代理
    在这里插入图片描述

    • 上面最后一行代码使用jdk动态代理生成SqlSession代理对象
    • 第一个参数表示生成代理对象的类加载器,第二个参数表示被代理类对象,第三个参数表示代理实现类(里面主要是执行代理对象时要做的事情)

    4.3.mapper

    mapper的实例化依赖上面创建的sqlSessionTemplate

    在这里插入图片描述
    mapper接口在前面被封装成了MapperFactoryBean,实现了InitializingBean接口,所以应该重写该接口的方法afterPropertiesSet,但是在MapperFactoryBean类中没有重新这个方法,而他的父类SqlSessionDaoSupport也没重写,所以是在SqlSessionDaoSupport的父类DaoSupport类中重写的,所以初始化MapperFactoryBean时会先调用DaoSupport中的afterPropertiesSet方法。
    在这里插入图片描述
    然后执行MapperFactoryBean的checkDaoConfig方法
    在这里插入图片描述
    在这里插入图片描述

    mapper是什么时候放到Configuration中的呢,请看上面的checkDaoConfig(也可能是在实例化sqlSessionFactory过程中,通过xmlMapperBuilder加载进来的,两种方式最终都是调用mapperRegistry.addMapper方法加载进来)
    为啥是这两种方式呢,因为mybaitsplus可能没有写mapper.xml文件,所以在初始化sqlSessionFactory时不会解析xml,需要在这里进行addMapper操作。

    这个操作是在createBean时完成的,完成后 mapper接口就变成了MapperFactoryBean类型。并执行了MapperFactoryBean的父类的父类(DaoSupport)的afterPropertiesSet方法
    下面的操作是createBean之后(实例化初始化之后)的

    在这里插入图片描述

    getObjectForBeanInstance会调用MapperFactoryBean.getObject方法。

    初始化之后,因为MapperFactoryBean是FactoryBean,所以会执行MapperFactoryBean.getObject(这里执行的前提条件是有service注入了这个mapper,不然是不会调用这个方法的
    为什么呢 因为跟着spring的步骤,mapper接口在调用getBean方法时会加&前缀,然后getObjectForBeanInstance判断如果加了前缀则直接返回。但是通过service的属性注入调用的getbean方法则不会加&前缀,保证了MapperFactoryBean.getObject的方法调用

    在这里插入图片描述
    上面的getSqlSession()获取的就是上面创建的SqlSessionTemplate对象,这是mapper的SqlSession,然后继续执行SqlSessionTemplate.getMapper,
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    通过jdk代理方式创建的mapper代理对象,代理实现类为MapperProxy,以后有人调用mapper方法,就会调用代理对象的invoke方法了。也就是MapperProxy的invoke方法了。
    最后把实例化mapper的bean放到bean缓存池中,至此,实例化mapper结束。

    然后就是前端发请求调用controller,到service,到mapper,到数据库,具体是怎么执行查询了。

    4.4.总结

    1.实例化SqlSessionFactory的作用
    构建configuration对象,保证mybatis的各项配置生效。
    解析*mapper.xml文件,保证xml中的sql执行。(解析完也放到configuration属性中)
    构建mapper接口与代理工厂的映射关系,保证mapper实例化。
    2.实例化sqlSessionTemplate的作用
    封装SqlSessionFactory,替SqlSessionFactory工作,比如mapper接口的实例化,sql的执行。
    3.实例化mapper的作用
    创建mapper接口的代理,service中调用接口方法时,执行mapper代理的方法完成mapper接口的方法的执行。
    在这里插入图片描述

    5.执行mapper操作

    执行mapper是调用的MapperProxy.invoke,最终调用的MapperMethod的execute方法
    在这里插入图片描述
    执行测试会执行MapperProxy类的invoke方法
    在这里插入图片描述
    在这里插入图片描述
    测试为查询,且没有返回list之类的,所以查询一条
    在这里插入图片描述

    method和command来自MapperMethod的有参构造函数,去config中拿到sql类型和mapper中方法的全限定名
    在这里插入图片描述
    这里的sqlSession其实就是上面生成的sqlSessionTemplate,在初始化MapperMethod时候,通过SqlCommand从Configuration中的MappedStatement获取限定名和SQL类型(select|insert|update|delete),然后执行sqlSessionTemplate的selectOne方法。
    在这里插入图片描述
    继续执行selectone
    在这里插入图片描述
    在这里插入图片描述
    sqlSessionProxy是sqlSessionTemplate的一个成员,在创建sqlSessionTemplate时赋值,是SqlSession的代理,所以,接下来走SqlSessionInterceptor.invoke方法,通过实例化sqlSessionTemplate可以看出,实际用的是反射机制,执行sqlSession方法。
    在这里插入图片描述
    可以看出method是sqlsession的method,所以在执行method.invoke时会执行DefaultSqlSession的selectOne方法。
    在这里插入图片描述
    这里的sqlSession是与数据库交互的会话sqlSession(DefaultSqlSession),根据sqlSessionFactory中的Configuration属性中的Environment属性去构建(主要是dataSource属性,但是这里还不会根据datasource进行连接),注意与sqlSessionTemplate这个sqlSession的区别,然后通过method.invoke反射调用到具体的 DefaultSqlSession.selectList 方法。
    在这里插入图片描述
    在这里插入图片描述

    最终调用Executor方法执行,Executor有两个方法:BaseExecutor 和 CachingExecutor,分别代表两种缓存机制,BaseExecutor 是一级缓存机制使用,CachingExecutor是二级缓存机制使用(如果查询二级缓存中没有结果,则会调用BaseExecutor的方法查询一级缓存,然后把查询结果放到二级缓存)。
    所以先来到二级缓存查询
    在这里插入图片描述
    这里在查询前需要拿到sql和缓存key,sql存储在configuration的mappedStatements的sqlSource属性中,解析${} #{} 等动态sql。
    在这里插入图片描述
    继续执行SimpleExecutor的query方法
    在这里插入图片描述
    在这里插入图片描述
    然后执行doQuery
    在这里插入图片描述

      @Override
      public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;
        try {
        	//获取到configuration 
          Configuration configuration = ms.getConfiguration();
          //创建statementHandler
          StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            //调用 prepareStatement() 方法 获取到 Statement 对象 (真正执行静态SQl的接口)
          stmt = prepareStatement(handler, ms.getStatementLog());
          //调用 StatementHandler.query() 方法执行
          return handler.query(stmt, resultHandler);
        } finally {
          closeStatement(stmt);
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    prepareStatement方法
    这个方法的getConnection会调用HikariDataSource类的getConnection方法,根据datasource中的url等配置和数据库进行连接。
    在这里插入图片描述

    在这里插入图片描述
    这几个子类的意义:

    SimpleStatementHandler ,这个对应的 就是JDBC 中常用到的 Statement 接口,用于简单SQL的处理

    PreparedStatementHandler , 这个对应的就是JDBC中的 PreparedStatement,用于预编译SQL的处理

    CallableStatementHandler , 这个对应JDBC中 CallableStatement ,用于执行存储过程相关的处理

    RoutingStatementHandler,这个接口是以上三个接口的路由,没有实际操作,只是负责上面三个StatementHandler的创建及调用
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    最后获取结果
    在这里插入图片描述
    在这里插入图片描述
    这里为了测试改了下sql语句
    在这里插入图片描述
    数据库对应数据
    在这里插入图片描述
    这样一次完整的查询就走完了。
    流程:
    执行mapper方法,实际就是通过jdk代理机制,执行MapperMethod的execute方法。execute方法里会调用session的方法,这个session是sqlSessionTemplate,创建sqlSessionTemplate时创建了一个代理类,也就是SqlSessionInterceptor,然后通过sqlSessionTemplate的jdk代理机制,执行SqlSessionInterceptor的invoke方法,这个方法会获取到DefaultSqlSession,然后执行method.invoke方法,然后通过反射机制,执行DefaultSqlSession.selectList方法。通过configuration.getMappedStatement获取MappedStatement,这时找到了实际的sql了,然后到了Executor模块。执行SimpleExecutor的doQuery方法,然后执行PreparedStatementHandler的query方法,最终调用jdbc操作执行sql,最后解析结果并返回。

    在这里插入图片描述

    6.总结

    需要处理的问题应该就是:mapper是一个接口,接口不能实例化,也就是不能干活,想让他干活的话就要用动态代理,找一个代理类帮他干活。mapper.xml有具体的sql,想让它执行就要把mapper接口的方法和xml中的sql关联起来
    springboot整合mybaits的问题

    • mapper怎么被spring注册为beanDefinition
    • mapper怎么被实例化
    • mapper方法怎么被执行

    6.1.mapper怎么被spring注册为beanDefinition:

    mapperscan注解或mapper注解,并且mybatis要重写isCandidateComponent方法,保证是接口也要生成beanDefinition。

    6.2.mapper是接口怎么被实例化

    注册的时候将mapper接口的类型转换为MapperFactoryBean,然后通过MapperFactoryBean的getObject方法实现实例化(通过jdk代理生成了bean的代理对象)。

    6.3.相关属性初始化

    初始化的过程,创建SqlSessionFactory、SqlSessionTemplate、MapperScannerConfigurer的bean定义,放到IOC容器(beanFactory)中,这是基础。在此过程,通过MapperScannerConfigurer扫描指定包下的所有mapper接口生成beanDefinition,并放到bean定义注册表中(类型为:MapperFactoryBean)。

    • SqlSessionFactory
      初始化SqlSessionFactory主要是填充Configuration属性。这里有两个重要参数:MappedStatement和MapperRegistry。
      MappedStatement是解析mapper.xml后封装成的对象。通过XMLMapperBuilder实现。
      在这里也完成了mapper接口与mapper.xml的绑定。MapperRegistry类内的knownMappers缓存: key为namespace对应的dao的class,value为MapperProxyFactory。
    • SqlSessionTemplate
      使用SqlSessionTemplate构造器创建SqlSessionTemplate对象,其中用了jdk代理方式创建了SqlSession代理对象。也就是执行查询的时候调用SqlSessionTemplate的方法实际上调用的是SqlSession的方法。所有访问数据库的操作都是通过SqlSession来的。这么做是为了解耦Mapper和SqlSession。

    6.4.执行mapper

    执行mapper方法的过程,主要是先通过两个代理类,即先执行mapper代理实现类MapperProxy的invoke方法,然后执行SqlSessionTemplate代理实现类的invoke方法,然后进入DefaultSqlSession相应方法中,这里会根据mapper的限定名获取MappedStatement,然后调用Executor相应方法,而Executor是封装了jdbc的操作,所以最终是通过jdbc执行sql,最后再把执行的结果解析返回。

    整个SpringBoot整合Mybatis的过程,就是在spring容器初始化的过程中生成mapper的代理对象,然后在执行mapper方法的过程,利用代理机制,执行目标方法,最终底层通过jdbc执行sql。

    在这里插入图片描述

    参考链接
    https://blog.csdn.net/zhengguofeng0328/article/details/125945926
    https://blog.csdn.net/u013521882/article/details/120624374

  • 相关阅读:
    风控建模十二:数据淘金——如何从APP数据中挖掘出有效变量
    C++标准模板(STL)- 类型支持 (定宽整数类型)(int8_t,int_fast8_t,int_least8_t,intmax_t,intptr_t)
    Scrapy框架介绍
    Java 对象深拷贝工具类
    mysql函数
    探索MATLAB在计算机视觉与深度学习领域的实战应用
    基于nodejs的二手物物交换平台【毕业设计源码】
    独家巨献!阿里专家兼Github贡献者业“大师级Dubbo实战笔记”入门到成神
    学习太极创客 — MQTT 第二章(二)ESP8266 QoS 应用
    13、序列化和反序列化
  • 原文地址:https://blog.csdn.net/weixin_46666822/article/details/127968259