Mybatis(Plus)源码阅读路径

1,431 阅读32分钟

前言

具体的学习路径不再赘述,将和Transaction Management源码阅读路径类似,但是从公司内部分享的结果来看通过一两个小时的时间同事们对源码难以掌握,特此作出一下调整。

  • 将对所画的时序图加上数字表示步骤,尽量通过语言描述大致步骤并作出总结。
  • 确定学习目标中,是自己遇到的问题和疑惑,可能不能覆盖读者的问题和疑惑,因此决定加入经常被问及的问题,都会作出源码级别的解释。

官方文档

mybatis传送门

mybatisplus传送门

Quick Start

传送门

想要了解的模块/功能

MybaytisPlus是对Mybatis的封装,所以核心应该还是Mybatis。所以想了解的为 dao是如何被扫描和初始化的,以及sql是如何被执行的,plus会顺带提及。

使用场景和功能特性

半映射框架,比起Hibernate更加灵活。支持自定义SQL、存储过程以及高级映射。Mybatis免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作。Mybatis可以通过简单的XML或注解来配置和映射原始类型、接口和Java POJO为数据库中的数据库记录。

核心

SqlSessionFactoryBuilder(从XML、注解或Java配置代码来创建SqlSessionFactory)、SqlSessionFactory(创建SqlSession)、SqlSession(获取映射器实例和管理事务,请参考 Mybatis-Spring)、Mapper(映射器是一些绑定映射语句的接口从SqlSession中获得)、typeHandlers(如类型处理器,LongTypeHandler处理器处理将获得的NUMERIC 或 BIGINT转换成long/Long)、ObjectFactory对象工厂来实例化结果对象、拦截允许你在映射语句过程中的某一点进行拦截方法(包含Executor、ParameterHandler、ResultSetHandler、StatementHandler)的调用、事务管理器(transactionManager)大部分使用的类型是JDBC(直接使用了JDBC的提交和回滚,它依赖从数据源获得的连接来管理事务作用域)、数据源(dataSource)三种内建的数据源类型(type="[UNPOOLED、POOLED、JNDI]")

确定学习目标

遇到/常被问及的问题

心存疑问/好奇

  • @MapperScan是如何起作用的
  • dao是如何实例化的
  • SqlSessionFactory、SqlSessionTemplate是如何实例化的
  • 语句执行的背后处理
  • 怎么半用mybayis,比如自己注入sql,不使用mapper接口进行CRUD

代码示例&依赖&配置等

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.whf</groupId>
    <artifactId>spring</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring</name>
    <description>study spring</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>4.3.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatisplus-spring-boot-starter</artifactId>
            <version>1.0.5</version>
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-configuration-processor</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus</artifactId>
            <version>2.2.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.16</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
management.endpoints.web.exposure.include=*
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/my_test
spring.datasource.username=root
spring.datasource.password=159753qpzm
mybatis-plus.mapper-locations=lasspath*:sqlmap/**/*.xml
#主键类型  0:"数据库ID自增", 1:"用户输入ID",2:"全局唯一ID (数字类型唯一ID)", 3:"全局唯一ID UUID";
mybatis-plus.global-config.id-type=0
#字段策略 0:"忽略判断",1:"非 NULL 判断"),2:"非空判断"
mybatis-plus.global-config.field-strategy=2
#驼峰下划线转换
mybatis-plus.global-config.db-column-underline=true
#刷新mapper 调试神器
mybatis-plus.global-config.refresh-mapper=true
#逻辑删除配置
mybatis-plus.global-config.logic-delete-value=1
mybatis-plus.global-config.logic-not-delete-value=0
#自定义SQL注入器
mybatis-plus.global-config.sql-injector=com.baomidou.mybatisplus.mapper.LogicSqlInjector
mybatis-plus.configuration.map-underscore-to-camel-case=true
mybatis-plus.configuration.cache-enabled=false
mybatis-plus.configuration.call-setters-on-nulls=true
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# 每次查询结束都会清掉一级缓存 session:在同一个 sqlSession 内,对同样的查询将不再查询数据库,直接从缓存中获取
mybatis-plus.configuration.local-cache-scope=statement
package com.whf.spring;

import com.whf.spring.service.business.TryService;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
@WebAppConfiguration
class ApplicationTests {
    @Autowired
    private TryService tryService;

    @Test
    void testMybatis() {
        tryService.test();
    }

}
package com.whf.spring.service.business;

public interface TryService {

    void test();
}
package com.whf.spring.service.business.impl;

import com.whf.spring.annotation.SimpleAnnotation;
import com.whf.spring.service.UserService;
import com.whf.spring.service.business.TryService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;

@Service
@Scope
@Slf4j
public class TryServiceImpl
        implements TryService
{
    @Autowired
    private UserService userService;

    @Override
    public void test() {
        log.info("执行业务");
        userService.test();
    }
}
package com.whf.spring.service;

import com.baomidou.mybatisplus.service.IService;
import com.whf.spring.entity.UserDo;

public interface UserService extends IService<UserDo> {
   void test();
}
package com.whf.spring.service.impl;

import com.whf.spring.dao.UserDao;
import com.whf.spring.entity.UserDo;
import com.whf.spring.service.UserService;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.baomidou.mybatisplus.service.impl.ServiceImpl;

@Service("userService")
public class UserServiceImpl extends ServiceImpl<UserDao, UserDo> implements UserService {

    @Override
    public void test() {
        this.selectById(1);
    }
    
}

@MapperScan

mybatis之@MapperScan.drawio.png

  1. 毋庸置疑入口为AbstarctApplicationContext#refresh,进入invokeBeanFactoryPostProcessors方法。

  2. 调用PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors,按序处理各种BeanFactoryPostProcessors

  3. 调用PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors调用被给的BeanDefinitionRegistryPostProcessor beans。

  • 这里的BeanDefinitionRegistryPostProcessor为ConfigurationClassPostProcessor(用于Configuration类的引导处理,使用 <context:annotation-config/><context:component-scan/> 时默认注册。 否则,可以像任何其BeanFactoryPostProcessor一样手动声明。)
  1. 调用MapperScannerRegistrar#registerBeanDefinitions,拿到注释在Application上的@MapperScan,读取注解属性扫描dao进行注册。具体执行见下。

  2. 调用ClassPathMapperScanner#doScan,根据basePackages进行扫描。

  • ClassPathMapperScanner:通过 {@code basePackage}、{@code annotationClass} 或 {@code markerInterface} 注册映射器的 {@link ClassPathBeanDefinitionScanner}。 如果指定了 {@code annotationClass} 和/或 {@code markerInterface},则只会搜索指定的类型(搜索所有接口将被禁用)。

  • doScan:调用super.doScan搜索和注册所有候选对象。然后对注册的对象进行后处理,将其设置为MapperFactoryBeans。这里扫描出所有dao这里对dao的bean定义进行注册( super.doScan中进行处理),beanClass为MapperFactoryBean

  1. retrun回PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors。

小结

核心为:

  • MapperScannerRegistrar#registerBeanDefinitions(Mybatis-Spring包中),拿到注释在Application上的@MapperScan,读取注解属性扫描dao进行注册。

MapperScannerRegistrar类

一个importBeanDefinitionRegistrator,用于允许Mybatis mapper scanning注解configuration。使用@Enable注解允许通过@Component配置注册bean,而实现 {@code BeanDefinitionRegistryPostProcessor} 将适用于 XML 配置。

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
  /**用于加载资源(例如,类路径或文件系统资源)的策略接口。**/
  private ResourceLoader resourceLoader;

}

由在处理@Configuration类时注册附加bean定义的类实现的接口。在bean定义(工厂)级别(而不是@Bean方法/实例级别)的操作上很有用。

与@configuration和@ImportSelector一起,这种类型的类可以提供给@Import注解(或者也可以从ImportSelector返回)。

public interface ImportBeanDefinitionRegistrar {
}

registerBeanDefinitions

根据导入的Configuration类的给定注释元数据,注册需要的bean定义。

请注意,由于与Configuration类处理相关的生命周期约束,此处可能未注册{@link BeanDefinitionRegistryPostProcessor}类型。

默认实现委托给#registerBeanDefinitions(AnnotationMetadata,BeanDefinitionRegistry)。

/**
 * @param importingClassMetadata importing类的注解元数据
 * @param registry 当前bean定义注册 如DefaultListableBeanFactory
 */
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  //这里importingClassMetadata的introspectedClass为“com.whf.spring.Application”,取出@MapperScan注解的属性
  AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
  ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

  // this check is needed in Spring 3.1
  if (resourceLoader != null) {
    scanner.setResourceLoader(resourceLoader);
  }

  Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
  if (!Annotation.class.equals(annotationClass)) {
    scanner.setAnnotationClass(annotationClass);
  }

  Class<?> markerInterface = annoAttrs.getClass("markerInterface");
  if (!Class.class.equals(markerInterface)) {
    scanner.setMarkerInterface(markerInterface);
  }

  Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
  if (!BeanNameGenerator.class.equals(generatorClass)) {
    scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
  }

  Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
  if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
    scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
  }

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

  List<String> basePackages = new ArrayList<String>();
  for (String pkg : annoAttrs.getStringArray("value")) {
    if (StringUtils.hasText(pkg)) {
      basePackages.add(pkg);
    }
  }
  for (String pkg : annoAttrs.getStringArray("basePackages")) {
    if (StringUtils.hasText(pkg)) {
      basePackages.add(pkg);
    }
  }
  for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
    basePackages.add(ClassUtils.getPackageName(clazz));
  }
  scanner.registerFilters();
  //封装好scanner后进行扫描
  scanner.doScan(StringUtils.toStringArray(basePackages));
}

默认实现为空。这里会拿到注释在Application上的@MapperScan,会创建一个ClassPathMapperScanner(通过{@code basePackage}、{@code annotationClass} 或 {@code markerInterface}注册Mappers。如果指定了 {@code annotationClass} 和/或 {@code markerInterface},则只会搜索指定的类型(搜索所有接口将被禁用)。),用于扫描basePackages中的dao bean。

@MapperScan的属性影响范围

我们先看下@MapperScan有哪些属性。

使用Java Config时使用此注解注册MyBatis映射器接口。它通过MapperScannerRegistrar执行与MapperScannerConfigurer相同的工作。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
  /**
   * #basePackages()属性的别名。允许更简单的注解声明,例如;
   * @EnableMybatisMapperScanner("org.my.pkg")代替@EnableMyBatisMapperScanner(basePackages="org.my.pkg")。
   */
  String[] value() default {};
  
  /**
   * 扫描MyBatis接口的Base packages。注意,只有具有至少一种方法的接口才会被注册;具体的类将被忽略。
   *
   */
  String[] basePackages() default {};
  /**
   * #basePackages()的Type-safe替代方案,用于指定要扫描带注解组件的包。将扫描指定的每个类的包。
   * 考虑在每个包中创建一个特殊的无操作标记类或接口,除了被此属性引用外,没有其他用途。
   */
  Class<?>[] basePackageClasses() default {};
  
  /**
   * BeanNameGenerator类被用于命名在Spring container检测到的组件。
   */
  Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

  /**
   * 此属性指定扫描器将搜索的注解。
   * 扫描器将在base package中注册所有接口,这些接口也具有指定的注解。
   * 请注意,这可以与markerInterface结合使用。
   */
  Class<? extends Annotation> annotationClass() default Annotation.class;
  
  /**
   * 此属性指定扫描将搜索的父项。
   *
   * 扫描器将在base package中注册所有接口,这些接口也具有指定的接口作为父类。
   * 
   * 请注意,这可以与annotationClass结合使用。
   */
  Class<?> markerInterface() default Class.class;

  /**
   * 指定在Spring 上下文中多个的情况下使用哪个SqlSessionTemplate。通常只有在你有多个datasource时才需要这样做。
   */
  String sqlSessionTemplateRef() default "";
  /**
   * 指定在Spring上下文中多个的情况下使用哪个SqlSessionFactory。通常只有在你有多个datasource时才需要这样做。
   */
  String sqlSessionFactoryRef() default "";
  /**
   * 指定自定义MapperFactoryBean以将mybatis代理作为Spring bean返回。
   */
  Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;

}

#value()、#basePackages()、#basePackageClasses()、#nameGenerator()、#annotationClass()、#markerInterface()都是用来指定扫描哪些dao的,一般情况下只要用#value()、#basePackages()即可其他的可以不用过多关注。#sqlSessionTemplateRef()、#sqlSessionFactoryRef()在多数据源情况下需要指定。#factoryBean()用于给dao生成bean。

所以我们这里主要关注#basePackages()、#sqlSessionTemplateRef()、#sqlSessionFactoryRef()、#factoryBean()即可。

ClassPathMapperScanner#doScan

查看basePackages()的影响

ClassPathMapperScanner#doScan进行处理。

调用super.doScan搜索和注册所有候选对象。然后对注册的对象进行后处理,将其设置为MapperFactoryBeans。

@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
  //这里扫描出basePackages里面符合要求的dao,并进行注册
  Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

  if (beanDefinitions.isEmpty()) {
    logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
  } else {
    //置为MapperFactoryBean
    processBeanDefinitions(beanDefinitions);
  }

  return beanDefinitions;
}

ClassPathMapperScanner#processBeanDefinitions

查看sqlSessionTemplateRef()、sqlSessionFactoryRef()、factoryBean()的影响

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
  GenericBeanDefinition definition;
  for (BeanDefinitionHolder holder : beanDefinitions) {
    definition = (GenericBeanDefinition) holder.getBeanDefinition();

    if (logger.isDebugEnabled()) {
      logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() 
        + "' and '" + definition.getBeanClassName() + "' mapperInterface");
    }
    // mapper接口是这个bean的原始类型但是MapperFactoryBean为这个bean的真实类型
    // 原始类作为MapperFactoryBean的构造函数的参数
    definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
    // @MapperScan#factoryBean=MapperFacroryBean.class决定的
    definition.setBeanClass(this.mapperFactoryBean.getClass());

    definition.getPropertyValues().add("addToConfig", this.addToConfig);

    boolean explicitFactoryUsed = false;
    //@MapperScan#sqlSessionFactoryRef=""决定的
    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;
    }
    //@MapperScan#查看sqlSessionTemplateRef=""决定的
    if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
      if (explicitFactoryUsed) {
        logger.warn("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("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
      }
      definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
      explicitFactoryUsed = true;
    }
    //最后explicitFactoryUsed=false
    if (!explicitFactoryUsed) {
      if (logger.isDebugEnabled()) {
        logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
      }
     //设置为根据类型注入
     definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    }
  }
}

是用来设置dao的BeanDefinition的,这里的重点在于把dao置为MapperFactoryBean(是一个FactoryBean被用作一个对象暴露的工厂,而不是直接作为一个暴露自己的 bean 实例。),beanName不变便于后面进行初始化处理。

Dao的实例化

非完全实例化

mybatis之@MapperScan.drawio.png

1~5步骤查看@MapperScan章节。

  1. 返回PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors方法,这里关注的是invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory),包含DependsOnDatabaseInitializationPostProcessor。

  2. 调用PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors。

  3. 调用AnnotationDependsOnDatabaseInitializationDetector#detect。

  • 检测在给定的{@code beanFactory}中定义的依赖于数据库初始化(被DependsOnDatabaseInitialization注释)的bean(扫描bean定义)。如果没有检测到bean,则返回一个空集合。
  1. 调用AbstractBeanFactory#getType:(LString)LClass。
  • 确定具有给定名称的 bean 的类型。 进一步来说,确定 {@link #getBean} 将为给定名称返回的对象类型。这里允许factory进bean行初始化。name为userDao。
  1. 调用AbstractAutowireCapableBeanFactory#getSingletonFactoryBeanForTypeCheck。
  • 获取“shortcut”单例FactoryBean实例以用于{@code getObjectType()}调用,无需对FactoryBean进行完全的初始化。
  1. 调用AbstractAutowireCapableBeanFactory#createBeanInstance。
  2. 返回AbstractAutowireCapableBeanFactory#getSingletonFactoryBeanForTypeCheck。
  • 这里的factoryBean为userDao没有完全实例化(在这里dao已实例化成MapperFactoryBean了,但是部分属性没有注入会放入this.factoryBeanInstanceCache中。

小结

可以发现对dao bean的处理顺序是先调用PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors(同时还完善了BeanDefinitionRegistryPostProcessor)扫描出所有dao拿到bean定义,再调用PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors对dao bean进行非完全的初始化。PostProcessorRegistrationDelegate为工厂后处理器,对所有dao进行了实例化。

核心为:

  • 使用AnnotationDependsOnDatabaseInitializationDetector#detect方法,检测在给定的{@code beanFactory}中定义的依赖于数据库初始化(被DependsOnDatabaseInitialization注释)的bean(扫描bean定义,这里当然包含dao但是dao并没有使用@DependsOnDatabaseInitialization注解)。在检测过程中会进行非完全实例化,对没有没有完全实例化的dao放入this.factoryBeanInstanceCache中。

AnnotationDependsOnDatabaseInitializationDetector

检测被DependsOnDatabaseInitialization注解的bean。

class AnnotationDependsOnDatabaseInitializationDetector implements DependsOnDatabaseInitializationDetector {

	@Override
	public Set<String> detect(ConfigurableListableBeanFactory beanFactory) {
		Set<String> dependentBeans = new HashSet<>();
                //拿到所有beanFactory中的beanName,这里就包含dao
		for (String beanName : beanFactory.getBeanDefinitionNames()) {
                        //进行搜索,在搜索的过程中会对dao进行初始化
			if (beanFactory.findAnnotationOnBean(beanName, DependsOnDatabaseInitialization.class) != null) {
				dependentBeans.add(beanName);
			}
		}
		return dependentBeans;
	}

}

检测依赖于数据库初始化的 bean。 实现应该在 {@code META-INF/spring.factories} 下注册 org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitializationDetector

public interface DependsOnDatabaseInitializationDetector {

    Set<String> detect(ConfigurableListableBeanFactory beanFactory);

}

AbstractAutowireCapableBeanFactory#getSingletonFactoryBeanForTypeCheck

获取“shortcut”单例FactoryBean实例以用于{@code getObjectType()}调用,无需对FactoryBean(这里dao的包装类为MapperFactoryBean)进行完全的初始化。

@Nullable
private FactoryBean<?> getSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) {
   synchronized (getSingletonMutex()) {
      //先根据beanName获得factoryBeanInstanceCache(用于缓存未完成的FactoryBean实例)中的BeanWrapper
      BeanWrapper bw = this.factoryBeanInstanceCache.get(beanName);
      //有说明已经进行了初始化,直接返回
      if (bw != null) {
         return (FactoryBean<?>) bw.getWrappedInstance();
      }
      //没有单例对象
      Object beanInstance = getSingleton(beanName, false);
      if (beanInstance instanceof FactoryBean) {
         return (FactoryBean<?>) beanInstance;
      }
      //不是正在创建的单例
      if (isSingletonCurrentlyInCreation(beanName) ||
            (mbd.getFactoryBeanName() != null && isSingletonCurrentlyInCreation(mbd.getFactoryBeanName()))) {
         return null;
      }

      Object instance;
      try {
         // 标记这个bean当前正在创建,即使只是部分的。
         beforeSingletonCreation(beanName);
         // 给BeanPostProcessors一个机会返回一个代理而不是目标bean实例。
         instance = resolveBeforeInstantiation(beanName, mbd);
         if (instance == null) {
            //进行创建了
            bw = createBeanInstance(beanName, mbd, null);
            instance = bw.getWrappedInstance();
         }
      }
      catch (UnsatisfiedDependencyException ex) {
         // Don't swallow, probably misconfiguration...
         throw ex;
      }
      catch (BeanCreationException ex) {
         // Don't swallow a linkage error since it contains a full stacktrace on
         // first occurrence... and just a plain NoClassDefFoundError afterwards.
         if (ex.contains(LinkageError.class)) {
            throw ex;
         }
         // Instantiation failure, maybe too early...
         if (logger.isDebugEnabled()) {
            logger.debug("Bean creation exception on singleton FactoryBean type check: " + ex);
         }
         onSuppressedException(ex);
         return null;
      }
      finally {
         // 从singletonsCurrentlyInCreation移出
         afterSingletonCreation(beanName);
      }

      FactoryBean<?> fb = getFactoryBean(beanName, instance);
      if (bw != null) {
         //缓存未完成的FactoryBean实例
         this.factoryBeanInstanceCache.put(beanName, bw);
      }
      return fb;
   }
}

对dao进行非完全实例化,对没有没有完全实例化的dao放入this.factoryBeanInstanceCache中。

完全实例化

mybatis 之sqlSessionFactory初始化.drawio.png

  1. 毋庸置疑入口为AbstarctApplicationContext#refresh,进入finishBeanFactoryInitialization方法,实例化所有剩余的(non-lazt-init)单利对象。

  2. 调用AbstractAutowireCapableBeanFactory#createBean:(LString,LRootBeanDefinition,[LO),该类的中心方法-创建一个bean的实例,填充bean实例属性,应用post-processors等。这里beanName为“tryServiceImpl”。

  3. 调用AbstractAutowireCapableBeanFactory#populateBean,这里填充tryServiceImpl的属性UserService。

  4. 调用AbstractAutowireCapableBeanFactory#createBean:(LString,LRootBeanDefinition,[LO),该类的中心方法-创建一个bean的实例,填充bean实例属性,应用post-processors等。这里beanName为“userService”。

  5. 调用AbstractAutowireCapableBeanFactory#populateBean,这里填充userService的属性userDao。

  6. 调用AbstractAutowireCapableBeanFactory#createBean:(LString,LRootBeanDefinition,[LO),该类的中心方法-创建一个bean的实例,填充bean实例属性,应用post-processors等。这里beanName为“userDao”。

  7. 调用AbstractAutowireCapableBeanFactory#doCreateBean,实际创建指定的bean。Pre-creation处理此时已经发生,例如检查postProcessBeforeInstantiation回调。区分默认bean实例化、工厂方法的使用和自动装配构造函数。这里beanName为“userDao”。

  8. 调用AbstractAutowireCapableBeanFactory#populateBean,这里填充userDao的属行。

  9. 调用AbstractAutowireCapableBeanFactory#autowireByType。定义“autowire by type”(按类型的bean属性)行为抽象方法。这里beanName为“userDao”。

  10. 调用AbstractAutowireCapableBeanFactory#unsatisfiedNonSimpleProperties。返回不未得到满足的非简单 bean 属性数组。

  11. 返回AbstractAutowireCapableBeanFactory#autowireByType。处理、注册并返回依赖注入的bean。这里为beanName的属性({"sqlSessionFactory","sqlSessionTemplate"})。

  12. 调用DefaultListableBeanFactory#resolveDependency,解决针对此工厂中定义的 bean 的指定依赖项。这里的requestingBeanName="userDao",descriptor.methodName="setSqlSessionFactory"。

  13. 调用AbstractAutowireCapableBeanFactory#createBean,这里创建"sqlSessionFactory"。根据BeanDefinition需要使用工厂方法进行实例化。

  14. 调用ConstructorResolver#instantiateUsingFactoryMethod。这里的factorymethod为MybatisPlusAutoConfiguration#sqlSessionFactory,argsToUsr为DruidDataSourceWrapper

  15. 调用ConstructorResolver#instantiate。这里实例化sqlSessionFactory,使用在 BeanFactories 中使用的默认对象实例化策略-CglibSubclassingInstantiationStrategy。如果方法需要被容器覆盖以实现方法注入,则使用 CGLIB 动态生成子类。

  16. 调用MybatisPlusAutoConfiguration#sqlSessionFactory,根据mybatisplus配置和dataSource来进行sqlSessionFactory的bean初始化。

  17. 调用MybatisSqlSessionFactoryBean#afterPropertiesSet,在构建sqlsessionfactory之前检查一些必须信息。

  18. 调用MybatisSqlSessionFactoryBean#buildSqlSessionFactory,使用前面的配置构建SqlSessionFactory。

  19. 返回SqlSessionFactory对象给MybatisPlusAutoConfiguration#sqlSessionFactory。

小结

1-6步一目了然,从AbstarctApplicationContext#refresh开始调用finishBeanFactoryInitialization实例化所有剩余的(non-lazy-init)单例对象。开始createBean为"TryServiceImpl"嵌套实例化bean直到createBean为“userDao”。第7步的时候doCreateBean从this.factoryBeanInstanceCache中取出beanName为"userDao"的BeanWrapper(和dao的非完全实例化呼应)。 后面开始填充“userDao”的属性({"sqlSessionFactory","sqlSessionTemplate"})实例化。

核心为:

  • MybatisPlusAutoConfiguration#sqlSessionFactory,根据根据配置和dataSource来进行sqlSessionFactory的bean初始化,用于创建SqlSessions。
  • MybatisSqlSessionFactoryBean#buildSqlSessionFactory,构建{@code SqlSessionFactory}实例。默认实现使用标准的MyBatis {@code XMLConfigBuilder} API,基于Reader构建{@code SqlSessionFactory}实例。从 1.3.0 开始,可以直接指定一个 {@link Configuration} 实例(无需配置文件)。内部使用mybatis的SqlSessionFactoryBuilder构建SqlSessionFactory。

sqlSessionTemplate和sqlSessionFactory的构建类似,都是dao的属性,因此不再赘述。

SqlSessionFactory

根据配置和dataSource来进行构造SqlSessionFactory。

SqlSessionTemplate

线程安全,Spring 管理,SqlSession与Spring事务管理一起工作,以确保实际使用的SqlSession是与当前Spring事务相关联的。 此外,它还管理会话life-cycle,包括根据Spring事务配置根据需要关闭、提交或回滚会话。

该模版需要一个SqlSessionFactory来创建SqlSessions,作为构造函数参数传递。它还可以构造指示要使用的执行器类型,如果没有,则将使用会话工厂中定义的默认执行器类型。

该模板默认使用 {@code MyBatisExceptionTranslator} 将 MyBatis PersistenceExceptions 转换为未经检查的 DataAccessExceptions。

因为 SqlSessionTemplate 是线程安全的,所以单个实例可以被所有 DAO 共享;这样做还应该节省少量内存。这种模式可以在 Spring 配置文件中使用,如下所示:

<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
   <constructor-arg ref="sqlSessionFactory" />
</bean>

AbstractAutowireCapableBeanFactory#populateBean

使用bean定义的属性值填充给定BeanWrapper中的bean实例。

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
   if (bw == null) {
      if (mbd.hasPropertyValues()) {
         throw new BeanCreationException(
               mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");
      }
      else {
         // 没有属性直接retrun
         return;
      }
   }

   // 在设置属性之前,让任何InstantiationAwareBeanPostProcessors有机会修改bean的状态。例如,可以用于支持字段注入样式。
   if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
      for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
         if (!bp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
            return;
         }
      }
   }

   PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
   int resolvedAutowireMode = mbd.getResolvedAutowireMode();
   if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
      MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
      // Add property values based on autowire by name if applicable.
      if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {
         autowireByName(beanName, mbd, bw, newPvs);
      }
      // Add property values based on autowire by type if applicable.
      if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
         autowireByType(beanName, mbd, bw, newPvs);
      }
      pvs = newPvs;
   }

   boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
   boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);

   PropertyDescriptor[] filteredPds = null;
   if (hasInstAwareBpps) {
      if (pvs == null) {
         pvs = mbd.getPropertyValues();
      }
      //采用责任链模式,遍历所有缓存的InstantiationAwareBeanPostProcessor对该bean进行实例化后处理。(这里会对注入的属性进行初始化)
      for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
         PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
         if (pvsToUse == null) {
            if (filteredPds == null) {
               filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
            }
            pvsToUse = bp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
            if (pvsToUse == null) {
               return;
            }
         }
         pvs = pvsToUse;
      }
   }
   if (needsDepCheck) {
      if (filteredPds == null) {
         filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
      }
      checkDependencies(beanName, mbd, filteredPds, pvs);
   }

   if (pvs != null) {
      applyPropertyValues(beanName, mbd, bw, pvs);
   }
}

AbstractAutowireCapableBeanFactory#autowireByType

定义“autowire by type”(按类型的bean属性)行为抽象方法。

protected void autowireByType(
      String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {

   TypeConverter converter = getCustomTypeConverter();
   if (converter == null) {
      converter = bw;
   }

   Set<String> autowiredBeanNames = new LinkedHashSet<>(4);
   //返回不满足的非简单 bean 属性数组
   String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
   for (String propertyName : propertyNames) {
      try {
         PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName);
         // Don't try autowiring by type for type Object: never makes sense,
         // even if it technically is a unsatisfied, non-simple property.
         if (Object.class != pd.getPropertyType()) {
            MethodParameter methodParam = BeanUtils.getWriteMethodParameter(pd);
            // Do not allow eager init for type matching in case of a prioritized post-processor.
            boolean eager = !(bw.getWrappedInstance() instanceof PriorityOrdered);
            DependencyDescriptor desc = new AutowireByTypeDependencyDescriptor(methodParam, eager);
            //处理并返回依赖注入的bean
            Object autowiredArgument = resolveDependency(desc, beanName, autowiredBeanNames, converter);
            //add进pvs
            if (autowiredArgument != null) {
               pvs.add(propertyName, autowiredArgument);
            }
            for (String autowiredBeanName : autowiredBeanNames) {
               //注册依赖bean
               registerDependentBean(autowiredBeanName, beanName);
               if (logger.isTraceEnabled()) {
                  logger.trace("Autowiring by type from bean name '" + beanName + "' via property '" +
                        propertyName + "' to bean named '" + autowiredBeanName + "'");
               }
            }
            autowiredBeanNames.clear();
         }
      }
      catch (BeansException ex) {
         throw new UnsatisfiedDependencyException(mbd.getResourceDescription(), beanName, propertyName, ex);
      }
   }
}

加上{"sqlSessionFactory","sqlSessionTemplate"}最后得到的pvs是{"addToConfig","sqlSessionFactory","sqlSessionTemplate"}

AbstractAutowireCapableBeanFactory#unsatisfiedNonSimpleProperties

返回不未得到满足的非简单 bean 属性数组。 这些可能是对工厂中其他 bean 未得到满足的引用。 不包括基本属性或字符串等简单属性。 这里返回的是{"sqlSessionFactory","sqlSessionTemplate"}

protected String[] unsatisfiedNonSimpleProperties(AbstractBeanDefinition mbd, BeanWrapper bw) {
   Set<String> result = new TreeSet<>();
   PropertyValues pvs = mbd.getPropertyValues();
   PropertyDescriptor[] pds = bw.getPropertyDescriptors();
   for (PropertyDescriptor pd : pds) {
      if (pd.getWriteMethod() != null && !isExcludedFromDependencyCheck(pd) && !pvs.contains(pd.getName()) &&
            !BeanUtils.isSimpleProperty(pd.getPropertyType())) {
         result.add(pd.getName());
      }
   }
   return StringUtils.toStringArray(result);
}

ConstructorResolver#instantiateUsingFactoryMethod

用于解析构造函数和工厂方法的委托。

通过参数匹配执行构造函数解析。

class ConstructorResolver {

   private static final Object[] EMPTY_ARGS = new Object[0];

   /**
    * 缓存参数数组中自动装配参数的标记,将替换为 {@linkplain     #resolveAutowiredArgument 解析自动装配参数}。
    */
   private static final Object autowiredArgumentMarker = new Object();

   private static final NamedThreadLocal<InjectionPoint> currentInjectionPoint =
         new NamedThreadLocal<>("Current injection point");


   private final AbstractAutowireCapableBeanFactory beanFactory;

   private final Log logger;
}   
  • instantiateUsingFactoryMethod 使用命名工厂方法实例化bean。如果bean定义参数指定一个类,而不是“factory-bean”,或者使用依赖注入配置的工厂对象本身的实例变量,则该方法可能是静态的。实现需要迭代具有名称的静态或实例方法在RootBeanDefinition中指定(该方法可能被重载)并尝试与参数匹配。我们没有附加到构造函数args的类型,所以反复试验是唯一的方法。explicitArgs数组可能包含通过相应的getBean方法以编程方式传入的参数值。
public BeanWrapper instantiateUsingFactoryMethod(
      String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {

   BeanWrapperImpl bw = new BeanWrapperImpl();
   this.beanFactory.initBeanWrapper(bw);

   Object factoryBean;
   Class<?> factoryClass;
   boolean isStatic;
   //获得factoryBeanName为“com.baomidou.mybatisplus.spring.boot.starter.MybatisPlusAutoConfiguration”
   String factoryBeanName = mbd.getFactoryBeanName();
   if (factoryBeanName != null) {
      if (factoryBeanName.equals(beanName)) {
         throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName,
               "factory-bean reference points back to the same bean definition");
      }
      factoryBean = this.beanFactory.getBean(factoryBeanName);
      if (mbd.isSingleton() && this.beanFactory.containsSingleton(beanName)) {
         throw new ImplicitlyAppearedSingletonException();
      }
      this.beanFactory.registerDependentBean(factoryBeanName, beanName);
      factoryClass = factoryBean.getClass();
      isStatic = false;
   }
   else {
      // It's a static factory method on the bean class.
      if (!mbd.hasBeanClass()) {
         throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName,
               "bean definition declares neither a bean class nor a factory-bean reference");
      }
      factoryBean = null;
      factoryClass = mbd.getBeanClass();
      isStatic = true;
   }

   Method factoryMethodToUse = null;
   ArgumentsHolder argsHolderToUse = null;
   Object[] argsToUse = null;

   if (explicitArgs != null) {
      argsToUse = explicitArgs;
   }
   else {
      Object[] argsToResolve = null;
      synchronized (mbd.constructorArgumentLock) {
         factoryMethodToUse = (Method) mbd.resolvedConstructorOrFactoryMethod;
         if (factoryMethodToUse != null && mbd.constructorArgumentsResolved) {
            // Found a cached factory method...
            argsToUse = mbd.resolvedConstructorArguments;
            if (argsToUse == null) {
               argsToResolve = mbd.preparedConstructorArguments;
            }
         }
      }
      if (argsToResolve != null) {
         argsToUse = resolvePreparedArguments(beanName, mbd, bw, factoryMethodToUse, argsToResolve);
      }
   }

   if (factoryMethodToUse == null || argsToUse == null) {
      // 需要确定工厂方法...
      // 尝试所有该名称的方法去看是否匹配所有被给的参数
      factoryClass = ClassUtils.getUserClass(factoryClass);

      List<Method> candidates = null;
      if (mbd.isFactoryMethodUnique) {
         if (factoryMethodToUse == null) {
            factoryMethodToUse = mbd.getResolvedFactoryMethod();
         }
         if (factoryMethodToUse != null) {
            candidates = Collections.singletonList(factoryMethodToUse);
         }
      }
      if (candidates == null) {
         candidates = new ArrayList<>();
         Method[] rawCandidates = getCandidateMethods(factoryClass, mbd);
         for (Method candidate : rawCandidates) {
            if (Modifier.isStatic(candidate.getModifiers()) == isStatic && mbd.isFactoryMethod(candidate)) {
               candidates.add(candidate);
            }
         }
      }

      if (candidates.size() == 1 && explicitArgs == null && !mbd.hasConstructorArgumentValues()) {
         Method uniqueCandidate = candidates.get(0);
         if (uniqueCandidate.getParameterCount() == 0) {
            mbd.factoryMethodToIntrospect = uniqueCandidate;
            synchronized (mbd.constructorArgumentLock) {
               mbd.resolvedConstructorOrFactoryMethod = uniqueCandidate;
               mbd.constructorArgumentsResolved = true;
               mbd.resolvedConstructorArguments = EMPTY_ARGS;
            }
            bw.setBeanInstance(instantiate(beanName, mbd, factoryBean, uniqueCandidate, EMPTY_ARGS));
            return bw;
         }
      }

      if (candidates.size() > 1) {  // explicitly skip immutable singletonList
         candidates.sort(AutowireUtils.EXECUTABLE_COMPARATOR);
      }

      ConstructorArgumentValues resolvedValues = null;
      boolean autowiring = (mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR);
      int minTypeDiffWeight = Integer.MAX_VALUE;
      Set<Method> ambiguousFactoryMethods = null;

      int minNrOfArgs;
      if (explicitArgs != null) {
         minNrOfArgs = explicitArgs.length;
      }
      else {
         // We don't have arguments passed in programmatically, so we need to resolve the
         // arguments specified in the constructor arguments held in the bean definition.
         if (mbd.hasConstructorArgumentValues()) {
            ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
            resolvedValues = new ConstructorArgumentValues();
            minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
         }
         else {
            minNrOfArgs = 0;
         }
      }

      Deque<UnsatisfiedDependencyException> causes = null;

      for (Method candidate : candidates) {
         int parameterCount = candidate.getParameterCount();

         if (parameterCount >= minNrOfArgs) {
            ArgumentsHolder argsHolder;

            Class<?>[] paramTypes = candidate.getParameterTypes();
            if (explicitArgs != null) {
               // Explicit arguments given -> arguments length must match exactly.
               if (paramTypes.length != explicitArgs.length) {
                  continue;
               }
               argsHolder = new ArgumentsHolder(explicitArgs);
            }
            else {
               // Resolved constructor arguments: type conversion and/or autowiring necessary.
               try {
                  String[] paramNames = null;
                  ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();
                  if (pnd != null) {
                     paramNames = pnd.getParameterNames(candidate);
                  }
                  argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw,
                        paramTypes, paramNames, candidate, autowiring, candidates.size() == 1);
               }
               catch (UnsatisfiedDependencyException ex) {
                  if (logger.isTraceEnabled()) {
                     logger.trace("Ignoring factory method [" + candidate + "] of bean '" + beanName + "': " + ex);
                  }
                  // Swallow and try next overloaded factory method.
                  if (causes == null) {
                     causes = new ArrayDeque<>(1);
                  }
                  causes.add(ex);
                  continue;
               }
            }

            int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
                  argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
            // Choose this factory method if it represents the closest match.
            if (typeDiffWeight < minTypeDiffWeight) {
               factoryMethodToUse = candidate;
               argsHolderToUse = argsHolder;
               argsToUse = argsHolder.arguments;
               minTypeDiffWeight = typeDiffWeight;
               ambiguousFactoryMethods = null;
            }
            // Find out about ambiguity: In case of the same type difference weight
            // for methods with the same number of parameters, collect such candidates
            // and eventually raise an ambiguity exception.
            // However, only perform that check in non-lenient constructor resolution mode,
            // and explicitly ignore overridden methods (with the same parameter signature).
            else if (factoryMethodToUse != null && typeDiffWeight == minTypeDiffWeight &&
                  !mbd.isLenientConstructorResolution() &&
                  paramTypes.length == factoryMethodToUse.getParameterCount() &&
                  !Arrays.equals(paramTypes, factoryMethodToUse.getParameterTypes())) {
               if (ambiguousFactoryMethods == null) {
                  ambiguousFactoryMethods = new LinkedHashSet<>();
                  ambiguousFactoryMethods.add(factoryMethodToUse);
               }
               ambiguousFactoryMethods.add(candidate);
            }
         }
      }

      if (factoryMethodToUse == null || argsToUse == null) {
         if (causes != null) {
            UnsatisfiedDependencyException ex = causes.removeLast();
            for (Exception cause : causes) {
               this.beanFactory.onSuppressedException(cause);
            }
            throw ex;
         }
         List<String> argTypes = new ArrayList<>(minNrOfArgs);
         if (explicitArgs != null) {
            for (Object arg : explicitArgs) {
               argTypes.add(arg != null ? arg.getClass().getSimpleName() : "null");
            }
         }
         else if (resolvedValues != null) {
            Set<ValueHolder> valueHolders = new LinkedHashSet<>(resolvedValues.getArgumentCount());
            valueHolders.addAll(resolvedValues.getIndexedArgumentValues().values());
            valueHolders.addAll(resolvedValues.getGenericArgumentValues());
            for (ValueHolder value : valueHolders) {
               String argType = (value.getType() != null ? ClassUtils.getShortName(value.getType()) :
                     (value.getValue() != null ? value.getValue().getClass().getSimpleName() : "null"));
               argTypes.add(argType);
            }
         }
         String argDesc = StringUtils.collectionToCommaDelimitedString(argTypes);
         throw new BeanCreationException(mbd.getResourceDescription(), beanName,
               "No matching factory method found: " +
               (mbd.getFactoryBeanName() != null ?
                  "factory bean '" + mbd.getFactoryBeanName() + "'; " : "") +
               "factory method '" + mbd.getFactoryMethodName() + "(" + argDesc + ")'. " +
               "Check that a method with the specified name " +
               (minNrOfArgs > 0 ? "and arguments " : "") +
               "exists and that it is " +
               (isStatic ? "static" : "non-static") + ".");
      }
      else if (void.class == factoryMethodToUse.getReturnType()) {
         throw new BeanCreationException(mbd.getResourceDescription(), beanName,
               "Invalid factory method '" + mbd.getFactoryMethodName() +
               "': needs to have a non-void return type!");
      }
      else if (ambiguousFactoryMethods != null) {
         throw new BeanCreationException(mbd.getResourceDescription(), beanName,
               "Ambiguous factory method matches found in bean '" + beanName + "' " +
               "(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): " +
               ambiguousFactoryMethods);
      }

      if (explicitArgs == null && argsHolderToUse != null) {
         mbd.factoryMethodToIntrospect = factoryMethodToUse;
         argsHolderToUse.storeCache(mbd, factoryMethodToUse);
      }
   }
   //找到工厂方法后进行实例化
   bw.setBeanInstance(instantiate(beanName, mbd, factoryBean, factoryMethodToUse, argsToUse));
   return bw;
}

MybatisPlusAutoConfiguration#sqlSessionFactory

Mybatis的{@link EnableAutoConfiguration Auto-Configuration}。贡献一个{@link SqlSessionFactory}和一个{@link SqlSessionTemplate}。

如果使用了{@link org.mbatis.spring.annotation.MapperScan},或者配置文件被指定为属性,这些都会被考虑,否则这个auto-configuration将尝试根据在auto-configuration根包的里面或者下面的接口定义注册mapper。

@SuppressWarnings("ConstantConditions")
@org.springframework.context.annotation.Configuration
//classpath上有SqlSessionFactory.class、MybatisSqlSessionFactoryBean.class
@ConditionalOnClass({SqlSessionFactory.class, MybatisSqlSessionFactoryBean.class})
//beanfactory里面有DataSource bean
@ConditionalOnBean(DataSource.class)
//支持MybatisPlusProperties
@EnableConfigurationProperties(MybatisPlusProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisPlusAutoConfiguration {

    private static final Log logger = LogFactory.getLog(MybatisPlusAutoConfiguration.class);
    //mybatis-plus配置
    private final MybatisPlusProperties properties;

    private final Interceptor[] interceptors;

    private final ResourceLoader resourceLoader;

    private final DatabaseIdProvider databaseIdProvider;

    private final List<ConfigurationCustomizer> configurationCustomizers;

    public MybatisPlusAutoConfiguration(MybatisPlusProperties properties,
                                        ObjectProvider<Interceptor[]> interceptorsProvider,
                                        ResourceLoader resourceLoader,
                                        ObjectProvider<DatabaseIdProvider> databaseIdProvider,
                                        ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
        this.properties = properties;
        this.interceptors = interceptorsProvider.getIfAvailable();
        this.resourceLoader = resourceLoader;
        this.databaseIdProvider = databaseIdProvider.getIfAvailable();
        this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
    }
}
  • sqlSessionFactory(DataSource dataSource) 根据mybatisplus配置和dataSource来进行sqlSessionFactory的bean初始化。
@Bean
//beanfactory里没有sqlsessionfactory才执行
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    //提供一个非常简单的API来访问应用服务器中的资源。
    factory.setVfs(SpringBootVFS.class);
    //MyBatis xml配置文件的位置
    if (StringUtils.hasText(this.properties.getConfigLocation())) {
        factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
    }
    //用于自定义默认设置的 Configuration 对象。 如果指定了 {@link #configLocation},则不使用此属性。
    MybatisConfiguration configuration = this.properties.getConfiguration();
    if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
        configuration = new MybatisConfiguration();
    }
    if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
        for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
            customizer.customize(configuration);
        }
    }
    //设置xml驱动
    configuration.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class);
    factory.setConfiguration(configuration);
    //MyBatis配置的外部化属性。
    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());
    }
    // TODO 自定义枚举包
    if (StringUtils.hasLength(this.properties.getTypeEnumsPackage())) {
        factory.setTypeEnumsPackage(this.properties.getTypeEnumsPackage());
    }
    if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
        factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
    }
    if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
        factory.setMapperLocations(this.properties.resolveMapperLocations());
    }
    //mybatis-plus.global-config
    if (!ObjectUtils.isEmpty(this.properties.getGlobalConfig())) {
        factory.setGlobalConfig(this.properties.getGlobalConfig().convertGlobalConfiguration());
    }
    return factory.getObject();
}

MybatisSqlSessionFactoryBean

拷贝类 org.mybatis.spring.SqlSessionFactoryBean 修改方法 buildSqlSessionFactory() 加载自定义 MybatisXmlConfigBuilder。

public class MybatisSqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {

    private static final Log LOGGER = LogFactory.getLog(SqlSessionFactoryBean.class);
    //MyBatis xml配置文件的位置
    private Resource configLocation;
    //配置类
    private Configuration configuration;
    //MyBatis mapper文件的位置。
    private Resource[] mapperLocations;
    //数据资源
    private DataSource dataSource;
    //事务工厂
    private TransactionFactory transactionFactory;
    //MyBatis配置的外部化属性
    private Properties configurationProperties;

    private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();

    private SqlSessionFactory sqlSessionFactory;

    //EnvironmentAware requires spring 3.1
    //MybatisSqlSessionFactoryBean
    private String environment = MybatisSqlSessionFactoryBean.class.getSimpleName();

    private boolean failFast;

    private Interceptor[] plugins;

    private TypeHandler<?>[] typeHandlers;

    private String typeHandlersPackage;

    private Class<?>[] typeAliases;

    private String typeAliasesPackage;

    // TODO 自定义枚举包
    private String typeEnumsPackage;

    private Class<?> typeAliasesSuperType;

    //issue #19. No default provider.
    private DatabaseIdProvider databaseIdProvider;
    //提供一个非常简单的API来访问应用服务器中的资源。
    private Class<? extends VFS> vfs;

    private Cache cache;

    private ObjectFactory objectFactory;

    private ObjectWrapperFactory objectWrapperFactory;

    private GlobalConfiguration globalConfig = GlobalConfigUtils.defaults();

    // TODO 注入全局配置
    public void setGlobalConfig(GlobalConfiguration globalConfig) {
        this.globalConfig = globalConfig;
    }
}

afterPropertiesSet()

在设置所有 bean 属性并满足 {@link BeanFactoryAware}、{@code ApplicationContextAware} 等之后,由包含的 {@code BeanFactory} 调用。 这里在构建sqlsessionfactory之前检查一些必须信息。

@Override
public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
            "Property 'configuration' and 'configLocation' can not specified with together");

    this.sqlSessionFactory = buildSqlSessionFactory();
}

buildSqlSessionFactory()

构建一个{@code SqlSessionFactory}实例。

默认实现使用标准MyBatis{@code XMLCOnfigBuilder}API来构建基于Reader的{@code SqlSessionFactory}实例。

从1.3.0开始,可以直接指定一个{@link Configuration}实例(无需配置文件)。

protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

    Configuration configuration;

    // TODO 加载自定义 MybatisXmlConfigBuilder
    MybatisXMLConfigBuilder xmlConfigBuilder = null;
    if (this.configuration != null) {
        configuration = this.configuration;
        if (configuration.getVariables() == null) {
            configuration.setVariables(this.configurationProperties);
        } else if (this.configurationProperties != null) {
            configuration.getVariables().putAll(this.configurationProperties);
        }
    } else if (this.configLocation != null) {
        xmlConfigBuilder = new MybatisXMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
        configuration = xmlConfigBuilder.getConfiguration();
    } else {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
        }
        // TODO 使用自定义配置
        configuration = new MybatisConfiguration();
        if (this.configurationProperties != null) {
            configuration.setVariables(this.configurationProperties);
        }
    }

    if (this.objectFactory != null) {
        configuration.setObjectFactory(this.objectFactory);
    }

    if (this.objectWrapperFactory != null) {
        configuration.setObjectWrapperFactory(this.objectWrapperFactory);
    }

    if (this.vfs != null) {
        configuration.setVfsImpl(this.vfs);
    }

    if (hasLength(this.typeAliasesPackage)) {
        // TODO 支持自定义通配符
        String[] typeAliasPackageArray;
        if (typeAliasesPackage.contains("*") && !typeAliasesPackage.contains(",")
                && !typeAliasesPackage.contains(";")) {
            typeAliasPackageArray = PackageHelper.convertTypeAliasesPackage(typeAliasesPackage);
        } else {
            typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
                    ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
        }
        if (typeAliasPackageArray == null) {
            throw new MybatisPlusException("not find typeAliasesPackage:" + typeAliasesPackage);
        }
        for (String packageToScan : typeAliasPackageArray) {
            configuration.getTypeAliasRegistry().registerAliases(packageToScan,
                    typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
            }
        }
    }

    // TODO 自定义枚举类扫描处理
    if (hasLength(this.typeEnumsPackage)) {
        Set<Class> classes = null;
        if (typeEnumsPackage.contains("*") && !typeEnumsPackage.contains(",")
                && !typeEnumsPackage.contains(";")) {
            classes = PackageHelper.scanTypePackage(typeEnumsPackage);
        } else {
            String[] typeEnumsPackageArray = tokenizeToStringArray(this.typeEnumsPackage,
                    ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
            if (typeEnumsPackageArray == null) {
                throw new MybatisPlusException("not find typeEnumsPackage:" + typeEnumsPackage);
            }
            classes = new HashSet<Class>();
            for (String typePackage : typeEnumsPackageArray) {
                classes.addAll(PackageHelper.scanTypePackage(typePackage));
            }
        }
        // 取得类型转换注册器
        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        for (Class cls : classes) {
            if (cls.isEnum()) {
                if (IEnum.class.isAssignableFrom(cls)) {
                    typeHandlerRegistry.register(cls.getName(), com.baomidou.mybatisplus.handlers.EnumTypeHandler.class.getCanonicalName());
                } else {
                    // 使用原生 EnumOrdinalTypeHandler
                    typeHandlerRegistry.register(cls.getName(), org.apache.ibatis.type.EnumOrdinalTypeHandler.class.getCanonicalName());
                }
            }
        }
    }

    if (!isEmpty(this.typeAliases)) {
        for (Class<?> typeAlias : this.typeAliases) {
            configuration.getTypeAliasRegistry().registerAlias(typeAlias);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Registered type alias: '" + typeAlias + "'");
            }
        }
    }

    if (!isEmpty(this.plugins)) {
        for (Interceptor plugin : this.plugins) {
            configuration.addInterceptor(plugin);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Registered plugin: '" + plugin + "'");
            }
        }
    }

    if (hasLength(this.typeHandlersPackage)) {
        String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
                ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
        for (String packageToScan : typeHandlersPackageArray) {
            configuration.getTypeHandlerRegistry().register(packageToScan);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers");
            }
        }
    }

    if (!isEmpty(this.typeHandlers)) {
        for (TypeHandler<?> typeHandler : this.typeHandlers) {
            configuration.getTypeHandlerRegistry().register(typeHandler);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Registered type handler: '" + typeHandler + "'");
            }
        }
    }

    if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
        try {
            configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
        } catch (SQLException e) {
            throw new NestedIOException("Failed getting a databaseId", e);
        }
    }

    if (this.cache != null) {
        configuration.addCache(this.cache);
    }

    if (xmlConfigBuilder != null) {
        try {
            xmlConfigBuilder.parse();

            if (LOGGER.isDebugEnabled()) {
                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();
        }
    }

    if (this.transactionFactory == null) {
        this.transactionFactory = new SpringManagedTransactionFactory();
    }

    configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
    // 设置元数据相关
    GlobalConfigUtils.setMetaData(dataSource, globalConfig);
    SqlSessionFactory sqlSessionFactory = this.sqlSessionFactoryBuilder.build(configuration);
    // TODO SqlRunner
    SqlRunner.FACTORY = sqlSessionFactory;
    // TODO 缓存 sqlSessionFactory
    globalConfig.setSqlSessionFactory(sqlSessionFactory);
    // TODO 设置全局参数属性
    globalConfig.signGlobalConfig(sqlSessionFactory);
    if (!isEmpty(this.mapperLocations)) {
        if (globalConfig.isRefresh()) {
            //TODO 设置自动刷新配置 减少配置
            new MybatisMapperRefresh(this.mapperLocations, sqlSessionFactory, 2,
                    2, true);
        }
        for (Resource mapperLocation : this.mapperLocations) {
            if (mapperLocation == null) {
                continue;
            }

            try {
                // TODO  这里也换了噢噢噢噢
                XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                        configuration, mapperLocation.toString(), configuration.getSqlFragments());
                xmlMapperBuilder.parse();
            } catch (Exception e) {
                throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
            } finally {
                ErrorContext.instance().reset();
            }

            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
            }
        }
    } else {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
        }
    }
    return sqlSessionFactory;
}

代码很多其实没啥,就是根据根据配置构建sqlSessionFactory。

语句执行的背后处理

mybatis之语句执行1.drawio.png

  1. 调用AbstractAutowireCapableBeanFactory#createBean,创建一个bean的实例,填充bean实例属性,应用post-processors等。这里创建的beanName为“userDao”。

  2. 调用XMLMapperBuilder#parse,具体执行见下。在调用MybatisSqlSessionFactoryBean#buildSqlSessionFactory构建SqlSessionFactory过程中遍历mapperLocations(dao.xml)过程中解析xml。拆解完xml后向configuration中的loadedResources中添加“file [/.../mybatis之语句执行/target/classes/sqlmap/UserDao.xml]”

  3. 调用XMLMapperBuilder#bindMapperForNamespace,具体执行见下。向nammespace中添加mapper,这里向configuration中的loadedResources中添加“namespace:com.whf.spring.dao.UserDao”。

  4. 调用MybatisMapperRegistry#addMapper,具体执行见下。会记录已知的mapper,并开始解析。这里为“interface com.whf.spring.dao.UserDao”

  5. 调用MybatisMapperAnnotationBuilder#parse,具体执行见下。这里解析出dao的使用注释注入的sql和BaseMapper接口的sql。

  6. 调用MybatisMapperAnnotationBuilder#loadXmlResource,加载的是dao的xml。这里默认的xmlResource为"com/whf/spring/dao/UserDao.xml",这里已经加载过xml所以不继续加载。configuration中的loadedResources中已记录

  7. 返回MybatisMapperAnnotationBuilder#parse方法,继续解析。

  8. 调用AutoSqlInjector#injectSql,注入Dao继承的BaseMapper接口的sql,具体执行如下。

  9. 调用LogicSqlInjector#injectSelectByIdSql,注入查询SQL语句,具体执行如下。支持逻辑删除。正常查询调用AutoSqlInjector#injectSelectByIdSql。

  10. 调用AutoSqlInjector#injectSelectByIdSql,具体执行见下。

  11. 调用Configuration#addMappedStatement,添加初始化好的MappedStatement。

  12. 进入例子的UserServiceImpl#test,执行this.selectById。

  13. 调用ServiceImpl#selectById,根据ID查询,调用baseMapper.selectById,这里baseMapper被动态代理了,被代理类为dao,InvocationHandler为MapperProxy。

  14. 调用MapperProxy#invoke,具体执行见下,这里内部调用mapperMethod.execute。

  15. 调用MapperMethod#execute,具体执行见下,这里内部会调用sqlSession.selectOne。

  16. 调用SqlSessionTemplate#selectOne,内部调用this.sqlSessionProxy. selectOne,sqlSessionProxy为动态代理类代理SqlSession——使用的handler为SqlSessionTemplate$SqlSessionInterceptor。这里的statement为“com.whf.spring.dao.UserDao.selectById”,parameter为1。

  17. 调用SqlSessionTemplate$SqlSessionInterceptor#invoke,处理代理实例上的方法调用并返回结果,在调用方法之前会先获得SqlSession。这里的method为SqlSession#selectOne,args为{"com.whf.spring.dao.UserDao.selectById",1},proxy为第5步的sqlSessionProxy。

  18. 调用SqlSessionUtils#getSqlSession,具体执行见下,这里会从 Spring 事务管理器获取 SqlSession 或根据需要创建一个新的。

  19. 获得sqlSession后,返回继续执行invoke。

  20. 调用DefaultSqlSession#selectOne,内部调用this.selectList会取出第一个作为结果返回。这里statement为“com.whf.spring.dao.UserDao.selectById”,parameter为1。

  21. 调用DefaultSqlSession#selectList,具体执行见下。rowBounds为检索的界限,statement为“com.whf.spring.dao.UserDao.selectById”,parameter为1。

  22. 调用BaseExecutor#query:(LMappedStatement,LObject,LRowBounds,LResultHandler)LList,获得BoundSql对象、CacheKey对象。

  23. 调用SimpleExecutor#doQuery,具体执行见下。

  24. 调用PreparedStatementHandler#query,具体执行见下。

小结

在dao填充属性之后会进行对sql的注入,1-11步完成此工作,所有的sql都会注册在Configuration中,key为全类限定名.方法名。

ServiceImpl提供的selectById内部调用了baseMapper.selectById。baseMapper被动态代理了,被代理类为dao,InvocationHandler为MapperProxy,生成代理的时候创建了sqlSession为SqlSessionTemplate用于执行sql。执行的时候会获得sqlSession如果当前有事务就从当前事务中获取,如果没有就使用SqlSessionFactory新创建一个。

核心

从时序图的调用类目前可以划分为:
mybatisplus——ServiceImpl、MybatisMapperRegistry、MybatisMapperAnnotationBuilder、LogicSqlInjector
mybatis原生——MapperProxy、MapperMethod、Configuration、DefaultSqlSession、XMLMapperBuilder、PreparedStatementHandler
mybatis-spring——SqlSessionTemplate、SqlSessionUtils
可以看的出来mybatisplus不是核心处理(做了封装等),dao代理的生成、SqlSessionFactory等核心能力是mybatis原生提供的,事务的控制是mybatis-spring提供的。

XMLMapperBuilder

parse

在调用MybatisSqlSessionFactoryBean#buildSqlSessionFactory构建SqlSessionFactory过程中遍历mapperLocations(dao.xml)过程中解析xml。拆解完xml后向configuration中的loadedResources中添加“file [/Users/wanghaifeng/IdeaProjects/mybatis之语句执行/target/classes/sqlmap/UserDao.xml]”

public void parse() {
  //判断resource是否加载过。这里resource为“file [/Users/wanghaifeng/IdeaProjects/mybatis之语句执行/target/classes/sqlmap/UserDao.xml]”
  if (!configuration.isResourceLoaded(resource)) {
    //拆解xml
    configurationElement(parser.evalNode("/mapper"));
    configuration.addLoadedResource(resource);
    bindMapperForNamespace();
  }

  //解析ResultMap
  parsePendingResultMaps();
  //解析CacheNamespaceRef
  parsePendingCacheRefs();
  //解析statements
  parsePendingStatements();
}

bindMapperForNamespace

向nammespace中添加mapper,这里向configuration中的loadedResources中添加“namespace:com.whf.spring.dao.UserDao”

private void bindMapperForNamespace() {
  // 获得当前的命名空间。这里namespace为“com.whf.spring.dao.UserDao”
  String namespace = builderAssistant.getCurrentNamespace();
  if (namespace != null) {
    Class<?> boundType = null;
    try {
      //boundType为com.whf.spring.dao.UserDao
      boundType = Resources.classForName(namespace);
    } catch (ClassNotFoundException e) {
      //ignore, bound type is not required
    }
    if (boundType != null) {
      //判断是否加载过
      if (!configuration.hasMapper(boundType)) {
        // Spring 可能不知道真正的资源名称,因此我们设置了一个标志以防止再次从映射器接口加载此资源,请查看 MapperAnnotationBuilder#loadXmlResource
        configuration.addLoadedResource("namespace:" + namespace);
        //向配置中添加mapper。这里为“interface com.whf.spring.dao.UserDao”
        configuration.addMapper(boundType);
      }
    }
  }
}

MybatisMapperRegistry#addMapper

@Override
public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
        //判断之前有没有注入过
        if (hasMapper(type)) {
            return;
            // throw new BindingException("Type " + type +
            // " is already known to the MybatisPlusMapperRegistry.");
        }
        boolean loadCompleted = false;
        try {
            //放入map避免重复put
            knownMappers.put(type, new MapperProxyFactory<>(type));
            //在parser运行之前添加type是很重要的,否则mapper parser可能会自动尝试绑定。如果类型已经知,则不尝试。
            // TODO 自定义无 XML 注入
    //继承MapperAnnotationBuilder没有XML配置文件注入基础CRUD
            MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
            parser.parse();
            loadCompleted = true;
        } finally {
            if (!loadCompleted) {
                knownMappers.remove(type);
            }
        }
    }
}

MybatisMapperAnnotationBuilder

MybatisMapperAnnotationBuilder
继承MapperAnnotationBuilder没有XML配置文件注入基础CRUD方法。

parse

@Override
public void parse() {
    //这里为“interface com.whf.spring.dao.UserDao”
    String resource = type.toString();
    //会判断resource有没有加载过。这里resource为“namespace:com.whf.spring.dao.UserDao”
    if (!configuration.isResourceLoaded(resource)) {
        //加载的是dao的xml。这里默认的xmlResource为"com/whf/spring/dao/UserDao.xml",这里已经加载过xml所以不继续加载。configuration中的loadedResources中已记录
        loadXmlResource();
        configuration.addLoadedResource(resource);
        assistant.setCurrentNamespace(type.getName());
        //这里会看dao是否被@CacheNamespace注释,如果有就开启缓存
        parseCache();
        //这里会看dao是否被@CacheNamespaceRef注释,如果有dao就引用该缓存
        parseCacheRef();
        //取出所有dao的方法
        Method[] methods = type.getMethods();
        // TODO 注入 CURD 动态 SQL (应该在注解之前注入)
        // dao是BaseMapper的子类
        if (BaseMapper.class.isAssignableFrom(type)) {
            GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
        }
        for (Method method : methods) {
            try {
                // issue #237
                if (!method.isBridge()) {
                    parseStatement(method);
                }
            } catch (IncompleteElementException e) {
                configuration.addIncompleteMethod(new MethodResolver(this, method));
            }
        }
    }
    parsePendingMethods();
}

AutoSqlInjector

AutoSqlInjector
SQL 自动注入器。

injectSql

注入Dao继承的BaseMapper接口的sql

/**
 * <p>
 * 注入SQL
 * </p>
 *
 * @param builderAssistant
 * @param mapperClass 这里为com.whf.spring.dao.UserDao
 * @param modelClass 这里为com.whf.spring.entity.UserDo
 * @param table
 */
protected void injectSql(MapperBuilderAssistant builderAssistant, Class<?> mapperClass, Class<?> modelClass, TableInfo table) {
    /**
     * #148 表信息包含主键,注入主键相关方法
     */
    if (StringUtils.isNotEmpty(table.getKeyProperty())) {
        /** 删除 */
        this.injectDeleteByIdSql(false, mapperClass, modelClass, table);
        this.injectDeleteByIdSql(true, mapperClass, modelClass, table);
        /** 修改 */
        this.injectUpdateByIdSql(true, mapperClass, modelClass, table);
        this.injectUpdateByIdSql(false, mapperClass, modelClass, table);
        /** 查询 */
        this.injectSelectByIdSql(false, mapperClass, modelClass, table);
        this.injectSelectByIdSql(true, mapperClass, modelClass, table);
    } else {
        // 表不包含主键时 给予警告
        logger.warn(String.format("%s ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method.",
                modelClass.toString()));
    }
    /**
     * 正常注入无需主键方法
     */
    /** 插入 */
    this.injectInsertOneSql(true, mapperClass, modelClass, table);
    this.injectInsertOneSql(false, mapperClass, modelClass, table);
    /** 删除 */
    this.injectDeleteSql(mapperClass, modelClass, table);
    this.injectDeleteByMapSql(mapperClass, table);
    /** 修改 */
    this.injectUpdateSql(mapperClass, modelClass, table);
    /** 查询 */
    this.injectSelectByMapSql(mapperClass, modelClass, table);
    this.injectSelectOneSql(mapperClass, modelClass, table);
    this.injectSelectCountSql(mapperClass, modelClass, table);
    this.injectSelectListSql(SqlMethod.SELECT_LIST, mapperClass, modelClass, table);
    this.injectSelectListSql(SqlMethod.SELECT_PAGE, mapperClass, modelClass, table);
    this.injectSelectMapsSql(SqlMethod.SELECT_MAPS, mapperClass, modelClass, table);
    this.injectSelectMapsSql(SqlMethod.SELECT_MAPS_PAGE, mapperClass, modelClass, table);
    this.injectSelectObjsSql(SqlMethod.SELECT_OBJS, mapperClass, modelClass, table);
    /** 自定义方法 */
    this.inject(configuration, builderAssistant, mapperClass, modelClass, table);
}

LogicSqlInjector

LogicSqlInjector
SQL 自动注入逻辑处理器
1、支持逻辑删除

injectSelectByIdSql

/**
 * <p>
 * 注入查询 SQL 语句
 * </p>
 *
 * @param batch       是否为批量插入
 * @param mapperClass
 * @param modelClass
 * @param table
 */
@Override
protected void injectSelectByIdSql(boolean batch, Class<?> mapperClass, Class<?> modelClass, TableInfo table) {
    //判断是否被@TableLogic注释。这里没有走正常查询
    if (table.isLogicDelete()) {
        SqlMethod sqlMethod = SqlMethod.LOGIC_SELECT_BY_ID;
        SqlSource sqlSource;
        if (batch) {
            sqlMethod = SqlMethod.LOGIC_SELECT_BATCH_BY_IDS;
            StringBuilder ids = new StringBuilder();
            ids.append("\n<foreach item="item" index="index" collection="coll" separator=",">");
            ids.append("#{item}");
            ids.append("\n</foreach>");
            sqlSource = languageDriver.createSqlSource(configuration, String.format(sqlMethod.getSql(), sqlSelectColumns(table, false),
                table.getTableName(), table.getKeyColumn(), ids.toString(), getLogicDeleteSql(table)), modelClass);
        } else {
            sqlSource = new RawSqlSource(configuration, String.format(sqlMethod.getSql(), sqlSelectColumns(table, false), table.getTableName(),
                table.getKeyColumn(), table.getKeyProperty(), getLogicDeleteSql(table)), Object.class);
        }
        this.addSelectMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource, modelClass, table);
    } else {
        // 正常查询
        super.injectSelectByIdSql(batch, mapperClass, modelClass, table);
    }
}

AutoSqlInjector#injectSelectByIdSql

/**
 * <p>
 * 注入查询 SQL 语句
 * </p>
 *
 * @param batch       是否为批量插入
 * @param mapperClass
 * @param modelClass
 * @param table
 */
protected void injectSelectByIdSql(boolean batch, Class<?> mapperClass, Class<?> modelClass, TableInfo table) {
    //使用枚举定义通用的SQL。sql="SELECT %s FROM %s WHERE %s=#{%s}"
    SqlMethod sqlMethod = SqlMethod.SELECT_BY_ID;
    SqlSource sqlSource;
    if (batch) {
        sqlMethod = SqlMethod.SELECT_BATCH_BY_IDS;
        StringBuilder ids = new StringBuilder();
        ids.append("\n<foreach item="item" index="index" collection="coll" separator=",">");
        ids.append("#{item}");
        ids.append("\n</foreach>");
        sqlSource = languageDriver.createSqlSource(configuration, String.format(sqlMethod.getSql(),
                sqlSelectColumns(table, false), table.getTableName(), table.getKeyColumn(), ids.toString()), modelClass);
    } else {
        //configuration为MybatisConfiguration,sql为“SELECT id AS id,create_time AS createTime,update_time AS updateTime,is_delete AS isDelete,phone,user_name AS userName,head FROM user WHERE id=?”
        sqlSource = new RawSqlSource(configuration, String.format(sqlMethod.getSql(), sqlSelectColumns(table, false),
                table.getTableName(), table.getKeyColumn(), table.getKeyProperty()), Object.class);
    }
    this.addSelectMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource, modelClass, table);
}

MapperProxy#invoke

method为"public abstract java.lang.Object com.baomidou.mybatisplus.mapper.BaseMapper.selectById(java.io.Serializable)",args为{1}。

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    //如果是Object的方法直接调用
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    } else if (isDefaultMethod(method)) {//如果方法的声明类是接口并且该方法是public的就执行,普通dao方法进入此子句
      return invokeDefaultMethod(proxy, method, args);
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
  //mapper的方法都一样可以进行缓存
  final MapperMethod mapperMethod = cachedMapperMethod(method);
  return mapperMethod.execute(sqlSession, args);
}

private MapperMethod cachedMapperMethod(Method method) {
  MapperMethod mapperMethod = methodCache.get(method);
  if (mapperMethod == null) {
    mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
    methodCache.put(method, mapperMethod);
  }
  return mapperMethod;
}

/**
 * Backport of java.lang.reflect.Method#isDefault()
 */
private boolean isDefaultMethod(Method method) {
  return (method.getModifiers()
      & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC
      && method.getDeclaringClass().isInterface();
}

MapperMethod#execute

sqlSession为SqlSessionTemplate,args为{1}。 根据command的Type执行。

public Object execute(SqlSession sqlSession, Object[] args) {
  Object result;
  switch (command.getType()) {
    case INSERT: {
    Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
      break;
    }
    case UPDATE: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
      break;
    }
    case DELETE: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
      break;
    }
    case SELECT:
      if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {
        result = executeForMap(sqlSession, args);
      } else if (method.returnsCursor()) {
        result = executeForCursor(sqlSession, args);
      } else {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
      break;
    case FLUSH:
      result = sqlSession.flushStatements();
      break;
    default:
      throw new BindingException("Unknown execution method for: " + command.getName());
  }
  if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
    throw new BindingException("Mapper method '" + command.getName() 
        + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
  }
  return result;
}

SqlSessionTemplate

线程安全,Spring 管理,{@code SqlSession} 与 Spring 事务管理一起工作,以确保实际使用的 SqlSession 是与当前 Spring 事务相关联的。此外,它还管理会话生命周期,包括根据 Spring 事务配置根据需要关闭、提交或回滚会话。

该模版需要一个SqlSessionFactory来创建SqlSessions,作为构造函数参数传递。它还可以构造指示要使用的执行器类型,如果没有,则将使用session factory中定义的默认执行器类型。

该模板默认使用 {@code MyBatisExceptionTranslator} 将 MyBatis PersistenceExceptions 转换为未经检查的 DataAccessExceptions。

因为 SqlSessionTemplate 是线程安全的,所以单个实例可以被所有 DAO 共享; 这样做还应该节省少量内存。 这种模式可以在 Spring 配置文件中使用,如下所示:

 <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
   <constructor-arg ref="sqlSessionFactory" />
 </bean>
public class SqlSessionTemplate implements SqlSession, DisposableBean {

  private final SqlSessionFactory sqlSessionFactory;

  private final ExecutorType executorType;

  private final SqlSession sqlSessionProxy;

  private final PersistenceExceptionTranslator exceptionTranslator;

  /**
   * 使用作为参数提供的 {@code SqlSessionFactory} 构造 Spring 管理的 SqlSession。
   *
   * @param 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));
  }

  /**
   * 使用给定的 {@code SqlSessionFactory} 和 {@code ExecutorType} 构造 Spring 管理的 {@code SqlSession}。
   * 可以提供自定义的 {@code SQLExceptionTranslator} 作为参数,因此 MyBatis 抛出的任何 {@code PersistenceException} 都可以自定义转换为 {@code RuntimeException}。
   * {@code SQLExceptionTranslator} 也可以为 null,因此没有
将异常被转换并抛出 MyBatis 异常
   */
  public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
  }
}  

selectOne

/**
 * {@inheritDoc}
 */
@Override
public <T> T selectOne(String statement, Object parameter) {
  return this.sqlSessionProxy.<T> selectOne(statement, parameter);
}

SqlSessionUtils

处理 MyBatis SqlSession 生命周期。 它可以从 Spring {@code TransactionSynchronizationManager} 注册和获取 SqlSessions。 如果没有事务处于活动状态,也可以使用。

public final class SqlSessionUtils {

  private static final Log LOGGER = LogFactory.getLog(SqlSessionUtils.class);

  private static final String NO_EXECUTOR_TYPE_SPECIFIED = "No ExecutorType specified";
  private static final String NO_SQL_SESSION_FACTORY_SPECIFIED = "No SqlSessionFactory specified";
  private static final String NO_SQL_SESSION_SPECIFIED = "No SqlSession specified";

  /**
   * 这个类不可以被实例化,仅公开静态实用方法。
   */
  private SqlSessionUtils() {
    // do nothing
  }
}  

从 Spring 事务管理器获取 SqlSession 或根据需要创建一个新的。尝试从当前事务中获取 SqlSession。 如果没有,它会创建一个新的。然后,如果 Spring TX 处于活动状态,它将 SqlSession 与事务同步并且SpringManagedTransactionFactory 被配置为事务管理器。

getSqlSession

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {

  notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
  notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
  //当前线程没有事务所以holder是null
  SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

  SqlSession session = sessionHolder(executorType, holder);
  if (session != null) {
    return session;
  }

  if (LOGGER.isDebugEnabled()) {
    LOGGER.debug("Creating a new SqlSession");
  }
  //获得一个新的sqlSession
  session = sessionFactory.openSession(executorType);
  //不存证事务不注册
  registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

  return session;
}

这里会直接创建一个新的sqlSession不会注册到TransactionSynchronizationManager中,因为没有开启事务。

DefaultSqlSession

使用MyBatis的主要java接口。通过此接口,您可以执行命令、获取mappers和管理事务。

public class DefaultSqlSession implements SqlSession {

  private final Configuration configuration;
  private final Executor executor;

  private final boolean autoCommit;
  private boolean dirty;
  private List<Cursor<?>> cursorList;

  public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
    this.configuration = configuration;
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;
  }

  public DefaultSqlSession(Configuration configuration, Executor executor) {
    this(configuration, executor, false);
  }
}  

selectList

rowBounds为检索的界限。 statement为“com.whf.spring.dao.UserDao.selectById”, parameter为1

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
    //拿到要执行的语句,已经装配好了
    MappedStatement ms = configuration.getMappedStatement(statement);
    //执行
    return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

SimpleExecutor#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);
    //创建statement
    stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.<E>query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
  Connection connection = getConnection(statementLog);
  stmt = handler.prepare(connection, transaction.getTimeout());
  handler.parameterize(stmt);
  return stmt;
}

PreparedStatementHandler#query

@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  //可以看到最终执行的还是jdbc的原生execute
  ps.execute();
  return resultSetHandler.<E> handleResultSets(ps);
}

半用mybatis

说下半用mybatis的目的:除了有利于对源码的了解,当mybatis plus无法满足你的需求时你可以自己进行扩展plus,有些大厂就是自己写的plus。

所以下面我们将半用mybatis自己往Configuration注入sql并执行。

从上面语句执行的时序图我们可以得出SqlSessionTemplate是mybatis和spring事务连接并执行语句的关键处理类。所以我们将从它入手进行实现半用。下面直接上代码并对代码进行解释。

package com.whf.spring.service.impl;

import com.whf.spring.dao.UserDao;
import com.whf.spring.entity.UserDo;
import com.whf.spring.service.UserService;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.baomidou.mybatisplus.service.impl.ServiceImpl;

@Service("userService")
public class UserServiceImpl extends ServiceImpl<UserDao, UserDo> implements UserService {
    @Autowired
    private SqlSessionTemplate sqlSessionTemplate;

    @Override
    public void test() {
        //mybatis配置类
        Configuration configuration = sqlSessionTemplate.getConfiguration();
        //表示从XML文件或者注解读取到的映射语句的内容。它创建将从用户接收的输入参数传递到数据库的SQL。
        SqlSource sqlSource = new RawSqlSource(configuration, " SELECT *\n" +
                "FROM\n" +
                "\t`user`\n" +
                "\tLIMIT 0,1", null);
        //mapper构建协助器        
        MapperBuilderAssistant mapperBuilderAssistant = new MapperBuilderAssistant(configuration, UserDao.class.getName().replace('.', '/') + ".java (best guess)");
        //设置命名空间
         mapperBuilderAssistant.setCurrentNamespace(UserDao.class.getName());
        //语句id同一个命名空间保证唯一
        String statementId = "getByMyId";
        //添加mappedStatement至configuration
        mapperBuilderAssistant.addMappedStatement(statementId, sqlSource, StatementType.STATEMENT, SqlCommandType.SELECT, null, null, null, null
                , null, UserDo.class, null, false, false, false, new NoKeyGenerator(), null, null, null
                , configuration.getDefaultScriptingLanguageInstance(), null);
        //进行查询
        UserDo userDo = sqlSessionTemplate.selectOne(UserDao.class.getName() + "." + statementId);
    }  
}

可以看出先对sql进行注入,方便后面sqlSessionTemplate根据statementId进行寻找MappedStatement。

总结

总体流程图如下

mybatisplus总体流程图.drawio.png

#{}和${}区别

#{xx}会被解析为一个参数占位符,参数会进行预编译防止进行sql注入,${}只是一个字符串占位符参数不会进行预编译直接把参数替换上去形成最终执行语句。

基于我们上面读过的源码我们直接从statementHandler入手来进行切入看下是怎么处理的。

例子在UserDao.xml添加一个动态传参sql

    <select id="getByName" resultType="com.whf.spring.entity.UserDo">
       SELECT *
FROM
   `user`
   where user_name = #{name}
   LIMIT 0,1
    </select>
    
    <select id="getByName1" resultType="com.whf.spring.entity.UserDo">
       SELECT *
FROM
	`user`
	where user_name = ${name}
	   LIMIT 0,1
    </select>    
import com.whf.spring.dao.UserDao;
import com.whf.spring.entity.UserDo;
import com.whf.spring.service.UserService;
import org.springframework.stereotype.Service;

import com.baomidou.mybatisplus.service.impl.ServiceImpl;

@Service("userService")
public class UserServiceImpl extends ServiceImpl<UserDao, UserDo> implements UserService {
    @Override
    public void test() {
        //这里我们知道mybatis会对#{}进行参数化?用‘’包住字符串所以使用' or 1=1 ;#'进行sql注入
        this.baseMapper.getByName("' or 1=1 ;#'");
        //这里我们知道mybatis会对${}直接字符串替换所以使用'' or 1=1 进行sql注入
        this.baseMapper.getByName1("'' or 1=1 ");
    }
}

mybatis之sql参数.drawio.png

  1. 第一步当然是进入我的service进行查询。

  2. 调用BaseExecutor#query:(LMappedStatement,LObject,LRowBounds,LResultHandler)LList,获得BoundSql对象、CacheKey对象。 getByName的boundsql.sql为 SELECT * FROM user where user_name = ? LIMIT 0,1。 getByName1的boundsql.sql为SELECT * FROM user where user_name = '' or 1=1 LIMIT 0,1

  3. 调用SimpleExecutor#doQuery,会进行预编译tatement和执行查询,这里我们主要关注预编译statement。

  4. 调用SimpleExecutor#prepareStatement,获得连接、预编译statement,这里我们主要关注statement的parameterize。

  5. 调用MybatisDefaultParameterHandler#setParameters,具体执行如下。

  6. 调用ClientPreparedStatement#setString,将指定的参数设置为给定的Java字符串值,具体执行如下。

  7. 调用ClientPreparedQueryBindings#setString,会进行转义,具体执行如下。最终会转化成

  8. 返回SimpleExecutor#doQuery。

  9. 最终调用PreparedStatementHandler#query进行查询。

总结

入口为Executor(在mybatis包中)在这里会进行连接的获得和预编译的调用,MybatisDefaultParameterHandler(在plus包中)用来提取参数,通过调用ClientPreparedStatement(在mysql驱动包中)进行不同类型参数的编译。#{}和${}的不同在第二步就可以看出来。

MybatisDefaultParameterHandler

本方法在预编译之前进行参数值获得/解析、TyperHandler获得、JdbcType获得。

@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public void setParameters(PreparedStatement ps) {
    // 反射获取动态参数,当dao参数是一个对象时使用
    Map<String, Object> additionalParameters = null;
    try {
        additionalParameters = (Map<String, Object>) additionalParametersField.get(boundSql);
    } catch (IllegalAccessException e) {
        // ignored, Because it will never happen.
    }
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
        for (int i = 0; i < parameterMappings.size(); i++) {
            ParameterMapping parameterMapping = parameterMappings.get(i);
            //这里为ParameterMode.IN
            if (parameterMapping.getMode() != ParameterMode.OUT) {
                Object value;
                String propertyName = parameterMapping.getProperty();
                if (boundSql.hasAdditionalParameter(propertyName)) {//issue#448 ask first for additional params
                    value = boundSql.getAdditionalParameter(propertyName);
                } else if (parameterObject == null) {
                    value = null;
                } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                    value = parameterObject;
                } else {
                    MetaObject metaObject = configuration.newMetaObject(parameterObject);
                    value = metaObject.getValue(propertyName);
                    if (value == null && MapUtils.isNotEmpty(additionalParameters)) {
                        // issue #138
                        value = additionalParameters.get(propertyName);
                    }
                }
                TypeHandler typeHandler = parameterMapping.getTypeHandler();
                JdbcType jdbcType = parameterMapping.getJdbcType();
                if (value == null && jdbcType == null) {
                    jdbcType = configuration.getJdbcTypeForNull();
                }
                try {
                    typeHandler.setParameter(ps, i + 1, value, jdbcType);
                } catch (TypeException | SQLException e) {
                    throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
                }
            }
        }
    }
}

ClientPreparedStatement

SQL语句经过预编译并存储在PreparedStatement对象中。然后可以使用此对象多次有效地执行此语句。
注意:用于设置IN参数值的setXXX方法必须指定与输入参数定义的SQL类型兼容的类型。例如,如果IN参数的SQL类型为Integer,则应使用使用setInt。
如果需要任意参数类型转换,则应将setObject方法与目标SQL类型一起使用。

public class ClientPreparedStatement extends com.mysql.cj.jdbc.StatementImpl implements JdbcPreparedStatement {

    /**
     * 批处理(如果有)是否包含由Statement.addBatch(String)添加的“普通”语句?
     * 如果是这样,我们不能重新编写它以使用多值或多查询。
     */
    protected boolean batchHasPlainStatements = false;

    protected MysqlParameterMetadata parameterMetaData;

    private java.sql.ResultSetMetaData pstmtResultMetaData;

    protected String batchedValuesClause;

    private boolean doPingInstead;

    private boolean compensateForOnDuplicateKeyUpdate = false;

    protected int rewrittenBatchSize = 0;

    protected static ClientPreparedStatement getInstance(JdbcConnection conn, String sql, String catalog) throws SQLException {
        return new ClientPreparedStatement(conn, sql, catalog);
    }
    
    protected static ClientPreparedStatement getInstance(JdbcConnection conn, String sql, String catalog, ParseInfo cachedParseInfo) throws SQLException {
        return new ClientPreparedStatement(conn, sql, catalog, cachedParseInfo);
    }
}    
@Override
public void setString(int parameterIndex, String x) throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
        ((PreparedQuery<?>) this.query).getQueryBindings().setString(getCoreParameterIndex(parameterIndex), x);
    }
}

ClientPreparedQueryBindings

@Override
public void setString(int parameterIndex, String x) {
    if (x == null) {
        setNull(parameterIndex);
    } else {
        int stringLength = x.length();
        // 服务器是否处于不允许我们使用\\转义的sql_mode中?,这里是false为允许。
        if (this.session.getServerSession().isNoBackslashEscapesSet()) {
            // Scan for any nasty chars

            boolean needsHexEscape = isEscapeNeededForString(x, stringLength);

            if (!needsHexEscape) {
                StringBuilder quotedString = new StringBuilder(x.length() + 2);
                quotedString.append(''');
                quotedString.append(x);
                quotedString.append(''');

                byte[] parameterAsBytes = this.isLoadDataQuery ? StringUtils.getBytes(quotedString.toString())
                        : StringUtils.getBytes(quotedString.toString(), this.charEncoding);
                setValue(parameterIndex, parameterAsBytes, MysqlType.VARCHAR);

            } else {
                byte[] parameterAsBytes = this.isLoadDataQuery ? StringUtils.getBytes(x) : StringUtils.getBytes(x, this.charEncoding);
                setBytes(parameterIndex, parameterAsBytes);
            }

            return;
        }

        String parameterAsString = x;
        boolean needsQuoted = true;
        //x为' or 1=1 ;#'包含'所以需要转义
        if (this.isLoadDataQuery || isEscapeNeededForString(x, stringLength)) {
            needsQuoted = false; // saves an allocation later

            StringBuilder buf = new StringBuilder((int) (x.length() * 1.1));

            buf.append(''');

            //
            // Note: buf.append(char) is _faster_ than appending in blocks, because the block append requires a System.arraycopy().... go figure...
            //

            for (int i = 0; i < stringLength; ++i) {
                char c = x.charAt(i);

                switch (c) {
                    case 0: /* Must be escaped for 'mysql' */
                        buf.append('\\');
                        buf.append('0');
                        break;
                    case '\n': /* Must be escaped for logs */
                        buf.append('\\');
                        buf.append('n');
                        break;
                    case '\r':
                        buf.append('\\');
                        buf.append('r');
                        break;
                    case '\\':
                        buf.append('\\');
                        buf.append('\\');
                        break;
                    case '\'':
                        buf.append('\\');
                        buf.append('\'');
                        break;
                    case '"': /* Better safe than sorry */
                        if (this.session.getServerSession().useAnsiQuotedIdentifiers()) {
                            buf.append('\\');
                        }
                        buf.append('"');
                        break;
                    case '\032': /* This gives problems on Win32 */
                        buf.append('\\');
                        buf.append('Z');
                        break;
                    case '\u00a5':
                    case '\u20a9':
                        // escape characters interpreted as backslash by mysql
                        if (this.charsetEncoder != null) {
                            CharBuffer cbuf = CharBuffer.allocate(1);
                            ByteBuffer bbuf = ByteBuffer.allocate(1);
                            cbuf.put(c);
                            cbuf.position(0);
                            this.charsetEncoder.encode(cbuf, bbuf, true);
                            if (bbuf.get(0) == '\') {
                                buf.append('\');
                            }
                        }
                        buf.append(c);
                        break;

                    default:
                        buf.append(c);
                }
            }

            buf.append(''');

            parameterAsString = buf.toString();
        }

        byte[] parameterAsBytes = this.isLoadDataQuery ? StringUtils.getBytes(parameterAsString)
                : (needsQuoted ? StringUtils.getBytesWrapped(parameterAsString, ''', ''', this.charEncoding)
                        : StringUtils.getBytes(parameterAsString, this.charEncoding));

        setValue(parameterIndex, parameterAsBytes, MysqlType.VARCHAR);
        
        
    }

一级缓存

代码/配置修改

import com.whf.spring.dao.UserDao;
import com.whf.spring.entity.UserDo;
import com.whf.spring.service.UserService;
import org.springframework.stereotype.Service;

import com.baomidou.mybatisplus.service.impl.ServiceImpl;

@Service("userService")
public class UserServiceImpl extends ServiceImpl<UserDao, UserDo> implements UserService {
    @Override
    public void test() {
        this.selectById(1L);
        this.selectById(1L);
    }
}
mybatis-plus.configuration.local-cache-scope=session

时序图 mybatis一级缓存.drawio.png

二级缓存

代码/配置修改

import com.whf.spring.dao.UserDao;
import com.whf.spring.entity.UserDo;
import com.whf.spring.service.UserService;
import org.springframework.stereotype.Service;

import com.baomidou.mybatisplus.service.impl.ServiceImpl;

@Service("userService")
public class UserServiceImpl extends ServiceImpl<UserDao, UserDo> implements UserService {
    @Override
    public void test() {
        this.selectById(1L);
        this.selectById(1L);
    }
}
mybatis-plus.configuration.cache-enabled=true
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.whf.spring.dao.UserDao">
    <cache/>
</mapper>

注意dao接口上使用了@CacheNamespaceRef(name = "com.whf.spring.dao.UserDao"),因为xml中加入只会让xml中的sql二级缓存生效,dao接口上其他通过注解注入的sql和继承mybatis plus的BaseMapper自带方法需要生效的话需要进行此注释。

import com.baomidou.mybatisplus.mapper.BaseMapper;
import com.whf.spring.entity.UserDo;
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.CacheNamespaceRef;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.Date;
@Mapper
@CacheNamespaceRef(name = "com.whf.spring.dao.UserDao")
public interface UserDao extends BaseMapper<UserDo> {
}

时序图

mybatis二级缓存.drawio.png