SpringBoot整合Mybatis源码解析

目录

一、示例代码

1.核心依赖

2.配置(application.yml)

3.核心代码

二、源码分析SpringBoot整合Mybatis的过程

1.SpringBoot集成Mybatis

2.创建MapperScannerConfigurer的bean定义及扫描mapper接口

3.实例化SqlSessionFactory、SqlSessionTemplate及mapper三个bean

3.1. 实例化SqlSessionFactory

3.2. 实例化sqlSessionTemplate

3.3. 实例化mapper

4.执行mapper操作原理

三、总结

1.如何对mapper实例化bean

1.1. 初始化

1.2. 实例化bean

2.如何执行mapper


在众多ORM框架中,mybatis算是比较常用的一个了,基于SpringBoot框架整合,mybatis的使用非常的方便,那么它的整合原理是怎样的呢?本篇将结合实例,通过源码分析SpringBoot整合Mybatis的过程。

以下实例及源码使用的SpringBoot、Mybatis及相关依赖版本如下:

  • SpringBoot:spring-boot-dependencies-2.3.5.RELEASE

  • Mybatis:mybatis-spring-boot-starter-2.2.0

  • JDK:1.8

为了更容易理解本篇的内容,建议提前了解下spring容器初始化过程以及jdk动态代理相关知识点。相关文章如下:

什么是spring容器和容器初始化;

JDK动态代理和CGLIB代理源码分析​​​​​​​;

一、示例代码

1.核心依赖


    org.springframework.boot
    spring-boot-dependencies
    2.3.5.RELEASE


    
        org.mybatis.spring.boot
        mybatis-spring-boot-starter
        2.2.0
    
    
        mysql
        mysql-connector-java
        5.1.35
    
    
        com.alibaba
        druid-spring-boot-starter
        1.1.9
    
    
        com.github.pagehelper
        pagehelper-spring-boot-starter
        1.3.0
    

2.配置(application.yml)

spring:
  datasource:
      name: test
      url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false&useOldAliasMetadataBehavior=true
      username: root
      password: ******
      # 使用druid数据源
      type: com.alibaba.druid.pool.DruidDataSource
      driver-class-name: com.mysql.jdbc.Driver
      filters: stat
      maxActive: 20
      initialSize: 1
      maxWait: 60000
      minIdle: 1
      timeBetweenEvictionRunsMillis: 60000
      minEvictableIdleTimeMillis: 300000
      validationQuery: select 'x'
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      poolPreparedStatements: true
      maxOpenPreparedStatements: 20
​
mybatis:
  #注意:一定要对应mapper映射xml文件的所在路径
  mapper-locations: classpath:mapping/*.xml
  # 注意:对应实体类的路径
  type-aliases-package: com.micro.demo.entity

3.核心代码

启动类ProviderApplication.java

package com.micro.provider;
​
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
​
@SpringBootApplication
@MapperScan("com.micro.provider.dao")
public class ProviderApplication {
​
    public static void main(String[] args) {
        SpringApplication.run(ProviderApplication.class, args);
    }
​
}

UserMapper.java类

package com.micro.provider.dao;
​
import com.micro.provider.dto.RequestParamDto;
import com.micro.provider.entity.User;
​
import java.util.List;
​
public interface UserMapper {
    int deleteByPrimaryKey(Long id);
​
    int insert(User record);
​
    int insertSelective(User record);
​
    User selectByPrimaryKey(Long id);
​
    int updateByPrimaryKeySelective(User record);
​
    int updateByPrimaryKey(User record);
​
    User validateUser(User user);
​
    List getUserLsit(RequestParamDto requestParamDto);
}

mapping/UserMapper.xml




  
    
    
    
    
    
    
    
    
    
  
  
    ID, USER_NAME, USER_NICKNAME, USER_PASSWORD, TELEPHONE, MAIL, CREATE_TIME, UPDATE_TIME, 
    IS_DEL
  
  
  
    delete from ims_user
    where ID = #{id,jdbcType=BIGINT}
  
  
    insert into ims_user (ID, USER_NAME, USER_NICKNAME, 
      USER_PASSWORD, TELEPHONE, MAIL, 
      CREATE_TIME, UPDATE_TIME, IS_DEL
      )
    values (#{id,jdbcType=BIGINT}, #{userName,jdbcType=VARCHAR}, #{userNickname,jdbcType=VARCHAR}, 
      #{userPassword,jdbcType=VARCHAR}, #{telephone,jdbcType=VARCHAR}, #{mail,jdbcType=VARCHAR}, 
      #{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP}, #{isDel,jdbcType=BIT}
      )
  
  
    insert into ims_user
    
      
        ID,
      
      
        USER_NAME,
      
      
        USER_NICKNAME,
      
      
        USER_PASSWORD,
      
      
        TELEPHONE,
      
      
        MAIL,
      
      
        CREATE_TIME,
      
      
        UPDATE_TIME,
      
      
        IS_DEL,
      
    
    
      
        #{id,jdbcType=BIGINT},
      
      
        #{userName,jdbcType=VARCHAR},
      
      
        #{userNickname,jdbcType=VARCHAR},
      
      
        #{userPassword,jdbcType=VARCHAR},
      
      
        #{telephone,jdbcType=VARCHAR},
      
      
        #{mail,jdbcType=VARCHAR},
      
      
        #{createTime,jdbcType=TIMESTAMP},
      
      
        #{updateTime,jdbcType=TIMESTAMP},
      
      
        #{isDel,jdbcType=BIT},
      
    
  
  
    update ims_user
    
      
        USER_NAME = #{userName,jdbcType=VARCHAR},
      
      
        USER_NICKNAME = #{userNickname,jdbcType=VARCHAR},
      
      
        USER_PASSWORD = #{userPassword,jdbcType=VARCHAR},
      
      
        TELEPHONE = #{telephone,jdbcType=VARCHAR},
      
      
        MAIL = #{mail,jdbcType=VARCHAR},
      
      
        CREATE_TIME = #{createTime,jdbcType=TIMESTAMP},
      
      
        UPDATE_TIME = #{updateTime,jdbcType=TIMESTAMP},
      
      
        IS_DEL = #{isDel,jdbcType=BIT},
      
    
    where ID = #{id,jdbcType=BIGINT}
  
  
    update ims_user
    set USER_NAME = #{userName,jdbcType=VARCHAR},
      USER_NICKNAME = #{userNickname,jdbcType=VARCHAR},
      USER_PASSWORD = #{userPassword,jdbcType=VARCHAR},
      TELEPHONE = #{telephone,jdbcType=VARCHAR},
      MAIL = #{mail,jdbcType=VARCHAR},
      CREATE_TIME = #{createTime,jdbcType=TIMESTAMP},
      UPDATE_TIME = #{updateTime,jdbcType=TIMESTAMP},
      IS_DEL = #{isDel,jdbcType=BIT}
    where ID = #{id,jdbcType=BIGINT}
  
  
  
public interface UserService {
    PageInfo getUserList(RequestParamDto requestParamDto);
}
@Service
public class UserServiceImpl implements UserService {
​
    @Autowired
    private UserMapper userMapper;
​
    /**
     * 获取用户列表
     * @param requestParamDto
     * @return
     */
    @Override
    public PageInfo getUserList(RequestParamDto requestParamDto){
        if(requestParamDto == null){
            requestParamDto = new RequestParamDto();
        }
        PageHelper.startPage(requestParamDto.getPageNum(), requestParamDto.getPageSize());
        List getUserLsit = userMapper.getUserLsit(requestParamDto);
        PageInfo pageInfo = new PageInfo<>(getUserLsit);
        return pageInfo;
    }
}
@Controller
@RequestMapping("/user")
public class UserController {
​
    @Autowired
    private UserService userService;
​
    /**
     * 用户首页
     * @return
     */
    @ResponseBody
    @RequestMapping("/index")
    public List index(@RequestParam(value = "pageNum", defaultValue = "1") int pageNum , @RequestParam(value = "keyword", defaultValue = "") String keyword){
        RequestParamDto requestParamDto = new RequestParamDto();
        requestParamDto.setKeyword(keyword);
        requestParamDto.setPageNum(pageNum);
        PageInfo getUserList = userService.getUserList(requestParamDto);
        return getUserList.getList();
    }
}

二、源码分析SpringBoot整合Mybatis的过程

基于上面的示例,接下来一起通过分析源码,看下SpringBoot整合Mybatis的过程。

1.SpringBoot集成Mybatis

由springboot引入包mybatis-spring-boot-starter后,该包中引入了mybatis-spring-boot-autoconfigure包,该包下的META-INF\spring.factories有如下配置,

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

而启动类ProviderApplication添加了@SpringBootApplication注解,内部源码如下,

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    Class[] exclude() default {};
​
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    String[] excludeName() default {};
​
    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};
​
    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class[] scanBasePackageClasses() default {};
​
    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "nameGenerator"
    )
    Class nameGenerator() default BeanNameGenerator.class;
​
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
​
    Class[] exclude() default {};
​
    String[] excludeName() default {};
}

注解@EnableAutoConfiguration的作用就是支持自动化配置机制,真正执行自动化配置是在SpringApplication的run方法里,执行run,(在执行到AbstractApplicationContext.refresh->invokeBeanFactoryPostProcessors方法里)类AutoConfigurationImportSelector会读取所有引入包下的META-INF\spring.factories文件,然后加载其中的类,加载之前会通过META-INF\spring-autoconfigure-metadata.properties文件中的条件进行判断,判断是否要加载META-INF\spring.factories中的类,在这里肯定时加载的(加载类中重点是MybatisAutoConfiguration类,我们稍后进行分析),详细加载过程在这里不做细究,感兴趣同学可以翻阅其他资料。

提前说明下,在加载MybatisAutoConfiguration类之后(注意此处并非实例化操作),因为SqlSessionTemplate、SqlSessionFactory都用了@Bean注解,所以,在执行refresh->obtainFreshBeanFactory()方法时,会把这两个类生成beanDefinition,并放入IOC容器(beanFactory)的bean定义注册表中。

2.创建MapperScannerConfigurer的bean定义及扫描mapper接口

在启动类使用了@MapperScan注解,先看下核心源码,

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MapperScannerRegistrar.class})
@Repeatable(MapperScans.class)
public @interface MapperScan {
    String[] value() default {};

    String[] basePackages() default {};

    Class[] basePackageClasses() default {};
}
@MapperScan注解通过@Import引入MapperScannerRegistrar类,MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,当执行refresh的invokeBeanFactoryPostProcessors会加载所有实现了ImportBeanDefinitionRegistrar接口的类,并执行它的registerBeanDefinitions方法,即执行MapperScannerRegistrar.registerBeanDefinitions,其源码如下,
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    // 获取@MapperScan注解属性信息
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
        this.registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
    }
​
}
​
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
    // 使用BeanDefinitionBuilder来构造MapperScannerConfigurer的BeanDefinition对象
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    builder.addPropertyValue("processPropertyPlaceHolders", true);
    Class annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
        builder.addPropertyValue("annotationClass", annotationClass);
    }
    // 从@MapperScan注解属性中获取设置的值,然后设置给MapperScannerConfigurer的bd
    Class markerInterface = annoAttrs.getClass("markerInterface");
    if (!Class.class.equals(markerInterface)) {
        builder.addPropertyValue("markerInterface", markerInterface);
    }
​
    Class generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
        builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
    }
​
    Class mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
        builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
    }
​
    String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
    if (StringUtils.hasText(sqlSessionTemplateRef)) {
        builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
    }
​
    String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
    if (StringUtils.hasText(sqlSessionFactoryRef)) {
        builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
    }
    // 获取@MapperScan注解的属性值,然后添加到basePackages集合中
    List basePackages = new ArrayList();
    basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
    basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));
    basePackages.addAll((Collection)Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList()));
    if (basePackages.isEmpty()) {
        basePackages.add(getDefaultBasePackage(annoMeta));
    }
​
    String lazyInitialization = annoAttrs.getString("lazyInitialization");
    if (StringUtils.hasText(lazyInitialization)) {
        builder.addPropertyValue("lazyInitialization", lazyInitialization);
    }
​
    String defaultScope = annoAttrs.getString("defaultScope");
    if (!"".equals(defaultScope)) {
        builder.addPropertyValue("defaultScope", defaultScope);
    }
    // 给BeanDefinition设置属性
    builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
    // 注册该MapperScannerConfigurer的bd
    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}

上面创建MapperScannerConfigurer的bean定义到beanFactory中,其中basePackages的值包含@MapperScan注解中的:com.micro.provider.dao。MapperScannerConfigurer的类继承关系如下图,

SpringBoot整合Mybatis源码解析_第1张图片

MapperScannerConfigurer实现的BeanDefinitionRegistryPostProcessor接口,因为在invokeBeanFactoryPostProcessors方法里会执行两个接口,按先后执行顺序为:

第一个是BeanDefinitionRegistryPostProcessor;

第二个是BeanFactoryPostProcessor。

而执行BeanDefinitionRegistryPostProcessor,会执行postProcessBeanDefinitionRegistry方法,在这里就是MapperScannerConfigurer.postProcessBeanDefinitionRegistry方法,源码如下,

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
        this.processPropertyPlaceHolders();
    }
	// 构建一个ClassPathMapperScanner,并填充相应属性
    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);
    }
	// 注册Filter,因为上面构造函数我们没有使用默认的Filter,
    // 有两种Filter,includeFilters:要扫描的;excludeFilters:要排除的
    scanner.registerFilters();
    // 扫描basePackage,basePackage可通过",; \t\n"来填写多个,
    // ClassPathMapperScanner重写了doScan方法
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
}
public int scan(String... basePackages) {
    int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
    // 进行真正的扫描,调用的ClassPathMapperScanner中的doScan
    this.doScan(basePackages);
    // Register annotation config processors, if necessary.
    if (this.includeAnnotationConfig) {
        AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    }
	// 返回注册BeanDefinition的个数
    return this.registry.getBeanDefinitionCount() - beanCountAtScanStart;
}
public Set doScan(String... basePackages) {
    // 调用父类(ClassPathBeanDefinitionScanner)去扫描指定包下面的类
    Set 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 {
        // 将扫描到的beanDefinitions进行处理,把beanDefinition的beanClass替换为
        this.processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
}
protected Set doScan(String... basePackages) {
    Assert.notEmpty(basePackages, "At least one base package must be specified");
    Set beanDefinitions = new LinkedHashSet();
    String[] var3 = basePackages;
    int var4 = basePackages.length;
	// 遍历传入的包名
    for(int var5 = 0; var5 < var4; ++var5) {
        String basePackage = var3[var5];
        // 扫描包路径下的所有mapper
        // 比如,则根据示例,我们配置的包为:com.micro.provider.dao,则会把包下UserMapper接口转换成BeanDefinition,并放入集合中返回
        Set candidates = this.findCandidateComponents(basePackage);
        Iterator var8 = candidates.iterator();

        while(var8.hasNext()) {
            BeanDefinition candidate = (BeanDefinition)var8.next();
            ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
            candidate.setScope(scopeMetadata.getScopeName());
            // 获取bean的名字
            String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
            if (candidate instanceof AbstractBeanDefinition) {
                //设置bean初始化相关属性
                this.postProcessBeanDefinition((AbstractBeanDefinition)candidate, beanName);
            }

            if (candidate instanceof AnnotatedBeanDefinition) {
                AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition)candidate);
            }
			// 判断beanFactory是否包含beanName的bean的定义,不包含则添加
            if (this.checkCandidate(beanName, candidate)) {
                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                beanDefinitions.add(definitionHolder);
                //把该BeanDefinition加到beanFactory的bean定义注册表中
                this.registerBeanDefinition(definitionHolder, this.registry);
            }
        }
    }

    return beanDefinitions;
}

上面主要的作用是扫描指定包下的类,并生成bean定义,然后注册到beanFactory的bean定义注册表中,并返回beanDefinitions。

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

返回这些刚生成的beanDefinitions后,继续执行processBeanDefinitions(beanDefinitions),源码如下,

private void processBeanDefinitions(Set beanDefinitions) {
    BeanDefinitionRegistry registry = this.getRegistry();
    Iterator var4 = beanDefinitions.iterator();

    while(var4.hasNext()) {
        BeanDefinitionHolder holder = (BeanDefinitionHolder)var4.next();
        AbstractBeanDefinition definition = (AbstractBeanDefinition)holder.getBeanDefinition();
        boolean scopedProxy = false;
        if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) {
            definition = (AbstractBeanDefinition)Optional.ofNullable(((RootBeanDefinition)definition).getDecoratedDefinition()).map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> {
                return new IllegalStateException("The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]");
            });
            scopedProxy = true;
        }

        String beanClassName = definition.getBeanClassName();
        LOGGER.debug(() -> {
            return "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName + "' mapperInterface";
        });
        // 获取参数构造器的,并未其添加值,其值为beanClassName--类的全限定名
        definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
        // 设置其BeanClass为mapperFactoryBeanClass,注意这个类实现了FactoryBean接口,
        // 在创建bean时,会根据BeanDefinition的BeanClass和构造方法进行创建
        definition.setBeanClass(this.mapperFactoryBeanClass);
        definition.getPropertyValues().add("addToConfig", this.addToConfig);
        definition.setAttribute("factoryBeanObjectType", beanClassName);
        boolean explicitFactoryUsed = false;
        // 因为sqlSessionFactoryBeanName、sqlSessionTemplateBeanName在@MapperScan中没有执行,所以下面的判断都不会进
        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(() -> {
                    return "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(() -> {
                    return "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.";
                });
            }

            definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
            explicitFactoryUsed = true;
        }

        if (!explicitFactoryUsed) {
            LOGGER.debug(() -> {
                return "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.";
            });
            definition.setAutowireMode(2);
        }

        definition.setLazyInit(this.lazyInitialization);
        if (!scopedProxy) {
            if ("singleton".equals(definition.getScope()) && this.defaultScope != null) {
                definition.setScope(this.defaultScope);
            }

            if (!definition.isSingleton()) {
                BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true);
                if (registry.containsBeanDefinition(proxyHolder.getBeanName())) {
                    registry.removeBeanDefinition(proxyHolder.getBeanName());
                }

                registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition());
            }
        }
    }

}

这里是把每个mapper的Bean定义的BeanClass设置为mapperFactoryBeanClass,这样做是为了让后面创建bean时,可以使用MapperFactoryBean来创建bean。

一般情况下,mapper是接口类。默认情况下,spring对接口interface是不会生成BeanDefinition 对象。在mybatis里,为了生成BeanDefinition 对象,则重写了isCandidateComponent方法,

protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}

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

因为mapper是接口,但是接口是不能实例化的,所以mybatis中就把mapper的beanDefinition的beanClass定义为mapperFactoryBeanClass,利用mapperFactoryBeanClass是通过getObject()来进行实例化,即通过jdk代理的方式,生成的代理对象,这块后面再具体分析。

我们可以下面图示看下,mapper生成的bean定义,转换BeanClass前后的对比,

SpringBoot整合Mybatis源码解析_第2张图片

转换后

SpringBoot整合Mybatis源码解析_第3张图片

简单地说,这里就是先生成MapperScannerConfigurer的bean定义,并放入bean定义注册表中。然后通过MapperScannerConfigurer.postProcessBeanDefinitionRegistry扫描所有mapper类,创建mapper的bean定义,也是放入bean定义注册表中,并将mapper的bean定义的BeanClass属性值设置为mapperFactoryBeanClass,为后面创建bean做准备。

扩展:如果不使用@MapperScan,会怎么处理?

加载MybatisAutoConfiguration类后,因为MybatisAutoConfiguration内部类AutoConfiguredMapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,当执行refresh的invokeBeanFactoryPostProcessors会加载所有实现了ImportBeanDefinitionRegistrar接口的类,并执行它的registerBeanDefinitions方法,即执行AutoConfiguredMapperScannerRegistrar.registerBeanDefinitions,其源码如下,

public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    if (!AutoConfigurationPackages.has(this.beanFactory)) {
        MybatisAutoConfiguration.logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
    } else {
        MybatisAutoConfiguration.logger.debug("Searching for mappers annotated with @Mapper");
        // 获取要扫描的包名,即在哪里找mapper
        // 对照示例,就是{"com.micro.provider"}
        List packages = AutoConfigurationPackages.get(this.beanFactory);
        if (MybatisAutoConfiguration.logger.isDebugEnabled()) {
            packages.forEach((pkg) -> {
                MybatisAutoConfiguration.logger.debug("Using auto-configuration base package '{}'", pkg);
            });
        }
		// 使用BeanDefinitionBuilder来构造MapperScannerConfigurer的BeanDefinition对象
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
        builder.addPropertyValue("processPropertyPlaceHolders", true);
        // 这就是要扫描的注解类型,就是@Mapper
        builder.addPropertyValue("annotationClass", Mapper.class);
        // 这里是要扫描的包的路径
        builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
        BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
        Set 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:}");
        }
		// 注册该MapperScannerConfigurer的bd
        registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
    }
}

这里也是生成MapperScannerConfigurer的bean定义,放到IOC容器(beanFactory)中,后面扫描mapper的逻辑和上面的是一个方法,但是执行后,发现没有找到mapper,报错:“Field userMapper in com.micro.provider.service.impl.UserServiceImpl required a bean of type 'com.micro.provider.dao.UserMapper' that could not be found.”,什么原因呢?

请看上面的代码,已经说明了会扫描带@Mapper注解的作为mapper,所以需要在mapper接口上添加@Mapper注解,经测试OK。

3.实例化SqlSessionFactory、SqlSessionTemplate及mapper三个bean

接下来,执行到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。

3.1. 实例化SqlSessionFactory

因为SqlSessionFactory在开始的MybatisAutoConfiguration就已经用@Bean进行注解,所以会执行到MybatisAutoConfiguration.sqlSessionFactory,此时dataSource已经初始化好了,这个不是重点,来看下sqlSessionFactory源码,

@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    factory.setVfs(SpringBootVFS.class);
    // 获取配置文件中设置的mybatis配置文件的路径
    if (StringUtils.hasText(this.properties.getConfigLocation())) {
        factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
    }
​
    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);
    }
    // 获取配置文件中设置的mapper配置文件的路径
    if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
        factory.setMapperLocations(this.properties.resolveMapperLocations());
    }
​
    Set factoryPropertyNames = (Set)Stream.of((new BeanWrapperImpl(SqlSessionFactoryBean.class)).getPropertyDescriptors()).map(FeatureDescriptor::getName).collect(Collectors.toSet());
    Class 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);
    }
​
    return factory.getObject();
}
public SqlSessionFactory getObject() throws Exception {
    // 如果没有sqlSessionFactory,则进行创建
    if (this.sqlSessionFactory == null) {
        this.afterPropertiesSet();
    }
​
    return this.sqlSessionFactory;
}
public void afterPropertiesSet() throws Exception {
    Assert.notNull(this.dataSource, "Property 'dataSource' is required");
    Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    Assert.state(this.configuration == null && this.configLocation == null || this.configuration == null || this.configLocation == null, "Property 'configuration' and 'configLocation' can not specified with together");
    // 创建sqlSessionFactory工厂
    this.sqlSessionFactory = this.buildSqlSessionFactory();
}
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
    XMLConfigBuilder xmlConfigBuilder = null;
    Configuration targetConfiguration;
    Optional var10000;
    if (this.configuration != null) {
        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 对象 : 用于解析 mybatis-config.xml 数据
        xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties);
        targetConfiguration = xmlConfigBuilder.getConfiguration();
    } else {
        LOGGER.debug(() -> {
            return "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration";
        });
        targetConfiguration = new Configuration();
        var10000 = Optional.ofNullable(this.configurationProperties);
        Objects.requireNonNull(targetConfiguration);
        var10000.ifPresent(targetConfiguration::setVariables);
    }
​
    var10000 = Optional.ofNullable(this.objectFactory);
    Objects.requireNonNull(targetConfiguration);
    var10000.ifPresent(targetConfiguration::setObjectFactory);
    var10000 = Optional.ofNullable(this.objectWrapperFactory);
    Objects.requireNonNull(targetConfiguration);
    var10000.ifPresent(targetConfiguration::setObjectWrapperFactory);
    var10000 = Optional.ofNullable(this.vfs);
    Objects.requireNonNull(targetConfiguration);
    var10000.ifPresent(targetConfiguration::setVfsImpl);
    Stream var24;
    if (StringUtils.hasLength(this.typeAliasesPackage)) {
        var24 = this.scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream().filter((clazz) -> {
            return !clazz.isAnonymousClass();
        }).filter((clazz) -> {
            return !clazz.isInterface();
        }).filter((clazz) -> {
            return !clazz.isMemberClass();
        });
        TypeAliasRegistry var10001 = targetConfiguration.getTypeAliasRegistry();
        Objects.requireNonNull(var10001);
        var24.forEach(var10001::registerAlias);
    }
​
    if (!ObjectUtils.isEmpty(this.typeAliases)) {
        Stream.of(this.typeAliases).forEach((typeAlias) -> {
            targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
            LOGGER.debug(() -> {
                return "Registered type alias: '" + typeAlias + "'";
            });
        });
    }
​
    if (!ObjectUtils.isEmpty(this.plugins)) {
        Stream.of(this.plugins).forEach((plugin) -> {
            targetConfiguration.addInterceptor(plugin);
            LOGGER.debug(() -> {
                return "Registered plugin: '" + plugin + "'";
            });
        });
    }
​
    if (StringUtils.hasLength(this.typeHandlersPackage)) {
        var24 = this.scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter((clazz) -> {
            return !clazz.isAnonymousClass();
        }).filter((clazz) -> {
            return !clazz.isInterface();
        }).filter((clazz) -> {
            return !Modifier.isAbstract(clazz.getModifiers());
        });
        TypeHandlerRegistry var25 = targetConfiguration.getTypeHandlerRegistry();
        Objects.requireNonNull(var25);
        var24.forEach(var25::register);
    }
​
    if (!ObjectUtils.isEmpty(this.typeHandlers)) {
        Stream.of(this.typeHandlers).forEach((typeHandler) -> {
            targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
            LOGGER.debug(() -> {
                return "Registered type handler: '" + typeHandler + "'";
            });
        });
    }
​
    targetConfiguration.setDefaultEnumTypeHandler(this.defaultEnumTypeHandler);
    if (!ObjectUtils.isEmpty(this.scriptingLanguageDrivers)) {
        Stream.of(this.scriptingLanguageDrivers).forEach((languageDriver) -> {
            targetConfiguration.getLanguageRegistry().register(languageDriver);
            LOGGER.debug(() -> {
                return "Registered scripting language driver: '" + languageDriver + "'";
            });
        });
    }
​
    var10000 = Optional.ofNullable(this.defaultScriptingLanguageDriver);
    Objects.requireNonNull(targetConfiguration);
    var10000.ifPresent(targetConfiguration::setDefaultScriptingLanguage);
    if (this.databaseIdProvider != null) {
        try {
            targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
        } catch (SQLException var23) {
            throw new NestedIOException("Failed getting a databaseId", var23);
        }
    }
​
    var10000 = Optional.ofNullable(this.cache);
    Objects.requireNonNull(targetConfiguration);
    var10000.ifPresent(targetConfiguration::addCache);
    if (xmlConfigBuilder != null) {
        try {
            // 执行 XmlConfigBuilder 解析方法
            xmlConfigBuilder.parse();
            LOGGER.debug(() -> {
                return "Parsed configuration file: '" + this.configLocation + "'";
            });
        } catch (Exception var21) {
            throw new NestedIOException("Failed to parse config resource: " + this.configLocation, var21);
        } finally {
            ErrorContext.instance().reset();
        }
    }
​
    targetConfiguration.setEnvironment(new Environment(this.environment, (TransactionFactory)(this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory), this.dataSource));
    if (this.mapperLocations != null) {
        if (this.mapperLocations.length == 0) {
            LOGGER.warn(() -> {
                return "Property 'mapperLocations' was specified but matching resources are not found.";
            });
        } else {
            Resource[] var3 = this.mapperLocations;
            int var4 = var3.length;
            // 遍历所有的mapper文件
            for(int var5 = 0; var5 < var4; ++var5) {
                Resource mapperLocation = var3[var5];
                if (mapperLocation != null) {
                    try {
                        // 创建 XMLMapperBuilder 对象 : 用于解析 mapper.xml 数据
                        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
                        xmlMapperBuilder.parse();
                    } catch (Exception var19) {
                        throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", var19);
                    } finally {
                        ErrorContext.instance().reset();
                    }
​
                    LOGGER.debug(() -> {
                        return "Parsed mapper file: '" + mapperLocation + "'";
                    });
                }
            }
        }
    } else {
        LOGGER.debug(() -> {
            return "Property 'mapperLocations' was not specified.";
        });
    }
    // 创建sqlSessionFactory对象
    return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}

总结,buildSqlSessionFactory就是通过XMLConfigBuilder解析mybatis配置,通过XMLMapperBuilder解析mapper.xml的配置(重点是生成mappedStatements、resultMaps、sqlFragments,结果看下图),以及其他的配置,最终放到Configuration里,供后面使用。

SpringBoot整合Mybatis源码解析_第4张图片

SpringBoot整合Mybatis源码解析_第5张图片

mappedStatements存放所有mapper.xml(上图是示例UserMapper.xml生成的mappedStatements)中的 select|delete|insert|update 操作,注意每个id操作生成两个value一样的MappedStatement,区别在于key不一样,一个使用的是方法名(sql语句中的id),一个使用的全限定名,这么做的原因是为了兼容早期版本的用法,早期不是通过的接口的,而是通过方法名的方式来进行查询的。而现在使用接口方式的话,在configuration.getMappedStatement的时候,是根据全限定名来获取MappedStatement。

SpringBoot整合Mybatis源码解析_第6张图片

resultMaps存放的就是resultMap。

SpringBoot整合Mybatis源码解析_第7张图片

sqlFragments存放的sql信息。

另外,通过XMLMapperBuilder解析生成的还有mapperRegistry、typeHandlerRegistry。

SpringBoot整合Mybatis源码解析_第8张图片

在XMLMapperBuilder.parse()里通过mapperRegistry.addMapper方法,会把mapper接口添加到knownMappers中(knownMappers结构为HashMap,每个mapper的value为MapperProxyFactory对象),那么后面调用getMapper的时候就可以直接获取了。所以XMLMapperBuilder.parse()方法,完成了mapper接口和xml映射文件的绑定。

SpringBoot整合Mybatis源码解析_第9张图片

在实际的入参或出参时,根据定义的参数类型,自动选择合适的typeHandler进行数据映射,typeHandler在mybatis里有默认的,当然也可以自定义typeHandler,需继承BaseTypeHandler,并重写相应的方法即可。

3.2. 实例化sqlSessionTemplate

接着开始实例化sqlSessionTemplate,同上面的SqlSessionFactory,调用的MybatisAutoConfiguration.sqlSessionTemplate,源码如下,

@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
	ExecutorType executorType = this.properties.getExecutorType();
	return executorType != null ? new SqlSessionTemplate(sqlSessionFactory, executorType) : new SqlSessionTemplate(sqlSessionFactory);
}
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
	this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
	this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
}
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
	Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
	Assert.notNull(executorType, "Property 'executorType' is required");
    // 设置sqlSessionFactory对象
	this.sqlSessionFactory = sqlSessionFactory;
	this.executorType = executorType;
	this.exceptionTranslator = exceptionTranslator;
    // 使用jdk动态代理生成SqlSession代理对象
    // 顶一个参数表示生成代理对象的类加载器,第二个参数表示被代理类对象,第三个参数表示代理实现类(里面主要是执行代理对象时要做的事情)
	this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
}

可以看出,使用jdk代理方式生成SqlSessionTemplate代理对象,代理实现是SqlSessionTemplate内部类SqlSessionInterceptor,

private class SqlSessionInterceptor implements InvocationHandler {
	private SqlSessionInterceptor() {
	}

	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 获取会话SqlSession(这里是DefaultSqlSession),该SqlSession可能是新创建的,也可能是上一次请求的SqlSession(属于同一事务)
		SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);

		Object unwrapped;
		try {
            // 利用反射机制执行SqlSession方法
			Object result = method.invoke(sqlSession, args);
            // 判断当前的 SqlSession 是否有事务,如果没有则commit
			if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
				sqlSession.commit(true);
			}

			unwrapped = result;
		} catch (Throwable var11) {
			unwrapped = ExceptionUtil.unwrapThrowable(var11);
			if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
                // 关闭当前会话,并且将sqlSession置为空,防止finally重复关闭
				SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
				sqlSession = null;
				Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
				if (translated != null) {
					unwrapped = translated;
				}
			}

			throw (Throwable)unwrapped;
		} finally {
            // 回话sqlSession不为空,则关闭回话sqlSession操作
			if (sqlSession != null) {
                // 如果当前sqlSession有事务,则进行释放;否则直接关闭sqlSession
				SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
			}

		}

		return unwrapped;
	}
}

3.3. 实例化mapper

上面已经实例化完成SqlSessionFactory和SqlSessionTemplate两个bean,现在终于到实例化mapper了。继续执行到initializeBean方法,然后执行到初始化方法invokeInitMethods。

SpringBoot整合Mybatis源码解析_第10张图片

因为mapper的beanClass是MapperFactoryBean,由上图可以看出,MapperFactoryBean实现了InitializingBean接口,所以先执行afterPropertiesSet,最终执行MapperFactoryBean.checkDaoConfig,源码如下,

public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
    this.checkDaoConfig();

    try {
        this.initDao();
    } catch (Exception var2) {
        throw new BeanInitializationException("Initialization of DAO failed", var2);
    }
}
protected void checkDaoConfig() {
    super.checkDaoConfig();
    Assert.notNull(this.mapperInterface, "Property 'mapperInterface' is required");
    Configuration configuration = this.getSqlSession().getConfiguration();
    // 如果configuration中没有该mapper接口,则加载
    // 注意,上面通过xmlMapperBuilder.parse()已经加载了mapper接口,所以这一步判断不会进入
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
        try {
            // 通过 addMapper() 方法加载到configuration中,内部走的也是mapperRegistry.addMapper方法
            configuration.addMapper(this.mapperInterface);
        } catch (Exception var6) {
            this.logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", var6);
            throw new IllegalArgumentException(var6);
        } finally {
            ErrorContext.instance().reset();
        }
    }

}

初始化之后,因为MapperFactoryBean是FactoryBean,所以会执行MapperFactoryBean.getObject,

public T getObject() throws Exception {
	return this.getSqlSession().getMapper(this.mapperInterface);
}

上面的getSqlSession()获取的就是上面创建的SqlSessionTemplate对象,这是mapper的SqlSession,然后继续执行SqlSessionTemplate.getMapper,源码如下,

public  T getMapper(Class type) {
	return this.getConfiguration().getMapper(type, this);
}

mapper是什么时候放到Configuration中的呢,请看上面的checkDaoConfig(也可能是在实例化sqlSessionFactory过程中,通过xmlMapperBuilder加载进来的,两种方式最终都是调用mapperRegistry.addMapper方法加载进来),明白了吧。继续往下看,

public  T getMapper(Class type, SqlSession sqlSession) {
    return this.mapperRegistry.getMapper(type, sqlSession);
}
public  T getMapper(Class type, SqlSession sqlSession) {
    // 从knownMappers中获取MapperProxyFactory
    MapperProxyFactory mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    } else {
        try {
            // 通过 MapperProxyFactory.newInstance 创建 MapperProxy
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception var5) {
            throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
        }
    }
}
public T newInstance(SqlSession sqlSession) {
    MapperProxy mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
    return this.newInstance(mapperProxy);
}
protected T newInstance(MapperProxy mapperProxy) {
    // 通过jdk动态代理方式创建mapper代理对象
    return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}

可以看出,也是通过jdk代理方式创建的mapper代理对象,代理实现类为MapperProxy,

public class MapperProxy implements InvocationHandler, Serializable {
    private static final long serialVersionUID = -4724728412955527868L;
    private static final int ALLOWED_MODES = 15;
    private static final Constructor lookupConstructor;
    private static final Method privateLookupInMethod;
    private final SqlSession sqlSession;
    private final Class mapperInterface;
    private final Map methodCache;
​
    public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
    }
​
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            // 如果调用的方法是 Object 种定义的方法,直接执行;否则获取缓存的MapperMethod方法,如果没有则创建    
            return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
    }
​
    private MapperProxy.MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
        try {
            return (MapperProxy.MapperMethodInvoker)MapUtil.computeIfAbsent(this.methodCache, method, (m) -> {
                if (m.isDefault()) {
                    try {
                        return privateLookupInMethod == null ? new MapperProxy.DefaultMethodInvoker(this.getMethodHandleJava8(method)) : new MapperProxy.DefaultMethodInvoker(this.getMethodHandleJava9(method));
                    } catch (InstantiationException | InvocationTargetException | NoSuchMethodException | IllegalAccessException var4) {
                        throw new RuntimeException(var4);
                    }
                } else {
                    return new MapperProxy.PlainMethodInvoker(new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration()));
                }
            });
        } catch (RuntimeException var4) {
            Throwable cause = var4.getCause();
            throw (Throwable)(cause == null ? var4 : cause);
        }
    }
​
    private MethodHandle getMethodHandleJava9(Method method) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        Class declaringClass = method.getDeclaringClass();
        return ((Lookup)privateLookupInMethod.invoke((Object)null, declaringClass, MethodHandles.lookup())).findSpecial(declaringClass, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()), declaringClass);
    }
​
    private MethodHandle getMethodHandleJava8(Method method) throws IllegalAccessException, InstantiationException, InvocationTargetException {
        Class declaringClass = method.getDeclaringClass();
        return ((Lookup)lookupConstructor.newInstance(declaringClass, 15)).unreflectSpecial(method, declaringClass);
    }
​
    static {
        Method privateLookupIn;
        try {
            privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class, Lookup.class);
        } catch (NoSuchMethodException var5) {
            privateLookupIn = null;
        }
​
        privateLookupInMethod = privateLookupIn;
        Constructor lookup = null;
        if (privateLookupInMethod == null) {
            try {
                lookup = Lookup.class.getDeclaredConstructor(Class.class, Integer.TYPE);
                lookup.setAccessible(true);
            } catch (NoSuchMethodException var3) {
                throw new IllegalStateException("There is neither 'privateLookupIn(Class, Lookup)' nor 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.", var3);
            } catch (Exception var4) {
                lookup = null;
            }
        }
​
        lookupConstructor = lookup;
    }
​
    private static class DefaultMethodInvoker implements MapperProxy.MapperMethodInvoker {
        private final MethodHandle methodHandle;
​
        public DefaultMethodInvoker(MethodHandle methodHandle) {
            this.methodHandle = methodHandle;
        }
​
        public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
            return this.methodHandle.bindTo(proxy).invokeWithArguments(args);
        }
    }
​
    private static class PlainMethodInvoker implements MapperProxy.MapperMethodInvoker {
        private final MapperMethod mapperMethod;
​
        public PlainMethodInvoker(MapperMethod mapperMethod) {
            this.mapperMethod = mapperMethod;
        }
​
        public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
            // 执行MapperMethod的execute方法
            return this.mapperMethod.execute(sqlSession, args);
        }
    }
​
    interface MapperMethodInvoker {
        Object invoke(Object var1, Method var2, Object[] var3, SqlSession var4) throws Throwable;
    }
}

最后把实例化mapper的bean放到bean缓存池中,至此,实例化mapper结束。

以上所有步骤,已经把SqlSessionFactory、SqlSessionTemplate、mapper这三个bean创建好了,那么实际使用mapper执行sql,是怎么实现的呢?接着往下看。

4.执行mapper操作原理

还记得前面创建的代理对象吗,通过Proxy和MapperProxy创建了mapper代理对象,所以执行mapper是调用的MapperProxy.invoke,最终调用的MapperMethod的execute方法,

这里我们使用controller里的/user/index请求为例,最终执行到userMapper.getUserLsit方法,

SpringBoot整合Mybatis源码解析_第11张图片

execute方法如下,

public Object execute(SqlSession sqlSession, Object[] args) {
	Object result;
	Object param;
	switch(this.command.getType()) {
	case INSERT:
		param = this.method.convertArgsToSqlCommandParam(args);
		result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
		break;
	case UPDATE:
		param = this.method.convertArgsToSqlCommandParam(args);
		result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
		break;
	case DELETE:
		param = this.method.convertArgsToSqlCommandParam(args);
		result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
		break;
	case SELECT:
		if (this.method.returnsVoid() && this.method.hasResultHandler()) {
			this.executeWithResultHandler(sqlSession, args);
			result = null;
		} else if (this.method.returnsMany()) {
			result = this.executeForMany(sqlSession, args);
		} else if (this.method.returnsMap()) {
			result = this.executeForMap(sqlSession, args);
		} else if (this.method.returnsCursor()) {
			result = this.executeForCursor(sqlSession, args);
		} else {
			param = this.method.convertArgsToSqlCommandParam(args);
			result = sqlSession.selectOne(this.command.getName(), param);
			if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
				result = Optional.ofNullable(result);
			}
		}
		break;
	case FLUSH:
		result = sqlSession.flushStatements();
		break;
	default:
		throw new BindingException("Unknown execution method for: " + this.command.getName());
	}

	if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
		throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
	} else {
		return result;
	}
}

execute里的sqlSession其实就是上面生成的sqlSessionTemplate,在初始化MapperMethod时候,通过SqlCommand从Configuration中的MappedStatement获取限定名和SQL类型(select|insert|update|delete)。

public MapperMethod(Class mapperInterface, Method method, Configuration config) {
    this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
    this.method = new MapperMethod.MethodSignature(config, mapperInterface, method);
}
public static class SqlCommand {
    private final String name;
    private final SqlCommandType type;

    public SqlCommand(Configuration configuration, Class mapperInterface, Method method) {
        String methodName = method.getName();
        Class declaringClass = method.getDeclaringClass();
        MappedStatement ms = this.resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration);
        if (ms == null) {
            if (method.getAnnotation(Flush.class) == null) {
                throw new BindingException("Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName);
            }

            this.name = null;
            this.type = SqlCommandType.FLUSH;
        } else {
            this.name = ms.getId();
            this.type = ms.getSqlCommandType();
            if (this.type == SqlCommandType.UNKNOWN) {
                throw new BindingException("Unknown execution method for: " + this.name);
            }
        }

    }
}

获取到的限定名name为“com.micro.provider.dao.UserMapper.getUserLsit”,SQL类型为“SELECT”。

然后根据类型判断,执行的executeForMany(sqlSession, args)方法,

private  Object executeForMany(SqlSession sqlSession, Object[] args) {
    Object param = this.method.convertArgsToSqlCommandParam(args);
    List result;
    if (this.method.hasRowBounds()) {
        RowBounds rowBounds = this.method.extractRowBounds(args);
        result = sqlSession.selectList(this.command.getName(), param, rowBounds);
    } else {
        // 到这里基本上就是到了真正处理逻辑的时候了,
        // sqlSession是通过SqlSessionTemplate构造器创建的,里面使用了jdk动态代理生成SqlSession代理对象
        result = sqlSession.selectList(this.command.getName(), param);
    }
​
    if (!this.method.getReturnType().isAssignableFrom(result.getClass())) {
        return this.method.getReturnType().isArray() ? this.convertToArray(result) : this.convertToDeclaredCollection(sqlSession.getConfiguration(), result);
    } else {
        return result;
    }
}

因为sqlSessionTemplate是SqlSessionInterceptor代理创建的,所以,接下来走SqlSessionInterceptor.invoke方法,通过上面实例化sqlSessionTemplate可以看出,实际用的是反射机制,执行sqlSession方法。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 获取会话SqlSession(这里是DefaultSqlSession),该SqlSession可能是新创建的,也可能是上一次请求的SqlSession(属于同一事务)
    SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
    // 忽略部分代码...
    // 利用反射机制执行SqlSession方法
    Object result = method.invoke(sqlSession, args);
    // 忽略部分代码...
​
    return unwrapped;
}

这里创建的sqlSession是与数据库交互的会话sqlSession,注意与sqlSessionTemplate这个sqlSession的区别,然后通过method.invoke反射调用到具体的 DefaultSqlSession.selectList 方法。

public  List selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
public  List selectList(String statement, Object parameter, RowBounds rowBounds) {
    return this.selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
}
private  List selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    List var6;
    try {
        // statement是由MapperMethod中的SqlCommand的name传下来的,即“com.micro.provider.dao.UserMapper.getUserLsit”
        // getMappedStatement利用限定名来获取对应的MappedStatement,这里就完成了从mapper接口到sql的连接了
        MappedStatement ms = this.configuration.getMappedStatement(statement);
        // 通过 Executor.query 执行数据库操作
        var6 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, handler);
    } catch (Exception var10) {
        throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var10, var10);
    } finally {
        ErrorContext.instance().reset();
    }
​
    return var6;
}

最终调用Executor方法执行,Executor有两个方法:BaseExecutor 和 CachingExecutor,分别代表两种缓存机制,BaseExecutor 是一级缓存机制使用,CachingExecutor是二级缓存机制使用(如果查询二级缓存中没有结果,则会调用BaseExecutor的方法查询一级缓存,然后把查询结果放到二级缓存)。

SpringBoot整合Mybatis源码解析_第12张图片

所以,一级缓存或二级缓存机制都会调用BaseExecutor,

public  List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 从 MappedStatement 中获取到 BoundSql(实际上是通过 调用 MappedStatement 中的 SqlSource  的 getBoundSql() 获取)
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 通过参数解析出 cacheKey ,这个是一级缓存的key
    CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql);
    return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

Mybatis默认的 BaseExecutor 实现是 SimpleExecutor,最终调用SimpleExecutor的doQuery方法,

public  List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
​
    List var9;
    try {
        // 从 MappedStatement 中获取到 Configuration
        Configuration configuration = ms.getConfiguration();
        // 通过 Configuration 的 newStatementHandler() 方法创建了一个 StatementHandler 对象
        StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        // 调用 prepareStatement() 方法 获取到 Statement 对象 (真正执行静态SQl的接口)
        stmt = this.prepareStatement(handler, ms.getStatementLog());
        // 调用 StatementHandler.query() 方法执行
        var9 = handler.query(stmt, resultHandler);
    } finally {
        this.closeStatement(stmt);
    }
​
    return var9;
}

StatementHandler的类继承关系,

SpringBoot整合Mybatis源码解析_第13张图片

这几个子类的意义:

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

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

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

  • RoutingStatementHandler,这个接口是以上三个接口的路由,没有实际操作,只是负责上面三个StatementHandler的创建及调用

由上面的configuration.newStatementHandler可以知道,创建的是RoutingStatementHandler,并且其内部 的 delegate 默认是 PreparedStatementHandler (MappedStatement builder方法指定了默认的 statementType = StatementType.PREPARED )

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    StatementHandler statementHandler = (StatementHandler)this.interceptorChain.pluginAll(statementHandler);
    return statementHandler;
}
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    switch(ms.getStatementType()) {
        case STATEMENT:
            this.delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
        case PREPARED:
            this.delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
        case CALLABLE:
            this.delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
        default:
            throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }
​
}

prepareStatement底层调用方法如下,

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    // 创建 Connection 链接,查看源码其是通过 transaction.getConnection() 获取到的
    Connection connection = this.getConnection(statementLog);
    // 预编译获取到 PrepareStatement ,即最终会调用到 connection.prepareStatement() 方法
    Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
    // 设置参数信息,其参数是通过从 BoundSql 获取
    handler.parameterize(stmt);
    return stmt;
}

执行handler.query方法,以PreparedStatementHandler为例,源码如下,

public  List query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement)statement;
    // 执行SQL
    ps.execute();
     // 通过 ResultSetHandler 的 handleResultSets() 方法解析返回数据
    return this.resultSetHandler.handleResultSets(ps);
}

这里的 ResultSetHandler就是用来处理 JDBC中的 ResultSet (相信用过JDBC 的同学对这个不陌生),关于如何解析返回数据的逻辑这里就不再详细分析了。

所以,Executor执行的操作,本质是底层封装了jdbc的一个执行操作。

返回结果后,如果当前SqlSession是否被事务所管控,如果是则不commit,最后再调用 closeSqlSession() 方法进行SqlSession “关闭”,这个关闭表示,判断当前SqlSession是否被事务所管控,是的话仅仅将引用计数器减一,并未真正将SqlSession 关闭(这也是为了下次能够使用同一个SqlSession),如果不被事务管控则执行正在的 session.close() 操作。

简单的说,执行mapper方法,实际就是通过jdk代理机制,执行MapperMethod的execute方法。然后通过sqlSessionTemplate的jdk代理机制,执行method.invoke方法。然后通过反射机制,执行DefaultSqlSession.selectList方法。通过configuration.getMappedStatement获取MappedStatement,这时找到了实际的sql了,然后到了Executor模块。Executor通过封装的jdbc操作,最终调用jdbc操作执行sql,最后解析结果并返回。

三、总结

整个SpringBoot整合Mybatis的过程,核心就两点:如何对mapper的bean实例化和如何执行mapper。

1.如何对mapper实例化bean

对mapper实例化bean,重点就是如何将mapper接口和mapper.xml映射文件的绑定,以及实例化mapper,具体分为两步。

1.1. 初始化

首先了解几个比较重要的类:

  • SqlSessionFactoryBean:用于生成SqlSessionFactory 的FactoryBean。

    • Configuration:存放所有mybatis配置信息,包括mapper接口、mapper.xml、 mybatis-config.xml等;

    • XMLConfigBuilder: 解析 mybatis-config.xml 配置并存放到Configuration中;

    • XMLMapperBuilder: 解析 mapper.xml 配置并存放到Configuration中,在这里完成了mapper接口与mapper.xml的绑定;

    • SqlSessionFactoryBuilder: 实际用于创建 SqlSessionFactory

    • SqlSessionFactory: 用于创建 SqlSession

    • SqlSession: Mybatis工作的最顶层API会话接口,所有访问数据库的操作都是通过SqlSession来的。

  • SqlSessionTemplate: 内部维护有 SqlSession 的代理对象,解耦Mapper和SqlSession的关键对象。

  • MapperScannerConfigurer:用于扫描所有mapper接口,并将mapper接口生成beanDefinition放到beanFactory的bean定义注册表中,然后再把beanDefinition中的mapper的beanClass转换成MapperFactoryBean,这么做是为了:第一,可以通过遍历bean定义注册表,找到mapper的beanDefinition,用于实例化bean;第二,可以通过MapperFactoryBean的getObject方法来实例化bean(通过jdk代理生成了bean的代理对象)。

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

1.2. 实例化bean

第一步,使用SqlSessionFactoryBean来生成SqlSessionFactory。生成过程中,使用了XMLConfigBuilder、XMLMapperBuilder解析mybatis相关的xml配置,放到Configuration中,然后放到SqlSessionFactory里,把创建的SqlSessionFactory实例放到bean缓存池中。

第二步,使用使用SqlSessionTemplate构造器创建SqlSessionTemplate对象,其中用了jdk代理方式创建了SqlSession代理对象。需说明,SqlSessionTemplate采用单例模式,并通过TransactionSynchronizationManager中的ThreadLocal>保存线程对应的SqlSession(即DefaultSqlSession,这个不是线程安全的),实现session的线程安全。

第三步,通过MapperFactoryBean来实例化mapper接口,也是通过jdk代理方式创建的mapper代理对象,并把依赖的SqlSessionFactory和SqlSessionTemplate注入mapper中。

2.如何执行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/qq_35634181/article/details/106396443

https://www.cnblogs.com/wbo112/p/15025348.html

https://www.cnblogs.com/bug9/p/11793728.html

https://www.cnblogs.com/bug9/p/11822093.html

https://www.cnblogs.com/bug9/p/11867615.html

https://www.cnblogs.com/bug9/p/11910005.html

你可能感兴趣的:(java,spring,mybatis,spring,boot,spring,Mybatis源码解析,mapper接口代理实现,SqlSession)