前言

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

1.mybatis的自动装配

引入mybatis-spring-boot-starter包。

<dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.0.0</version>
    </dependency>

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

1.1.配置数据源

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

/**
	 * Hikari DataSource configuration.
	 */
	@ConditionalOnClass(HikariDataSource.class)
	@ConditionalOnMissingBean(DataSource.class)
	@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource", matchIfMissing = true)
	static class Hikari {

		@Bean
		@ConfigurationProperties(prefix = "spring.datasource.hikari")
		public HikariDataSource dataSource(DataSourceProperties properties) {
			HikariDataSource dataSource = createDataSource(properties,
					HikariDataSource.class);
			if (StringUtils.hasText(properties.getName())) {
				dataSource.setPoolName(properties.getName());
			}
			return dataSource;
		}

	}

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

boot集成Mysql和mybatis spring mybatis集成到springboot_java

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

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

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

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

<dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

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

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
    </parent>

1.2.创建mapper

@Mapper
public interface CityMapper {

  @Select("SELECT * FROM CITY WHERE state = #{state}")
  City findByState(@Param("state") String state);

}

1.3.启动程序,运行sql

@SpringBootApplication
public class SampleMybatisApplication implements CommandLineRunner {

  private final CityMapper cityMapper;

  public SampleMybatisApplication(CityMapper cityMapper) {
    this.cityMapper = cityMapper;
  }

  public static void main(String[] args) {
    SpringApplication.run(SampleMybatisApplication.class, args);
  }

  @Override
  public void run(String... args) throws Exception {
    System.out.println(this.cityMapper.findByState("CA"));
  }

}

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

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

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

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

2.mybatis的自动装配过程

2.1.mybatis starter的装配

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

boot集成Mysql和mybatis spring mybatis集成到springboot_java_02

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

2.1.1.spring的加载bean过程

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

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

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

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

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

2.2.SqlSessionFactory和SqlSessionTemplate的装配

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

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

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

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

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());
  }

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

private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

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

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

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

4、关闭数据库连接

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

mapper -> SqlSessionTemplate -> sqlSessionProxy -> DefaultSqlSession

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

2.3.mapper的注册

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

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

2.3.1.@Mapper注册

@org.springframework.context.annotation.Configuration
  @Import({ AutoConfiguredMapperScannerRegistrar.class })
  @ConditionalOnMissingBean(MapperFactoryBean.class)
  public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {

    @Override
    public void afterPropertiesSet() {
      logger.debug("No {} found.", MapperFactoryBean.class.getName());
    }
  }

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

boot集成Mysql和mybatis spring mybatis集成到springboot_spring_03

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

GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      String beanClassName = definition.getBeanClassName();
      LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName()
          + "' and '" + beanClassName + "' mapperInterface");

      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
      //设置构造函数的参数,这里的参数值beanClassName为原始类,也就是mapper接口,这里设置了构造函数的参数值,那么后面实例化的时候,会调用带参数的构造函数来new对象,也就是MapperFactoryBean(Class<T> mapperInterface)这个方法,因为最终使用的是代理类,所以最终new的也是代理类
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
      //设置当前bean的类为MapperFactoryBean类,我们知道当前其实是一个mapper接口,这么设置后,后面实例化的就是MapperFactoryBean
      definition.setBeanClass(this.mapperFactoryBean.getClass());

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

      boolean explicitFactoryUsed = false;
      if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
        //设置sqlSessionFactory,@MapperScan会走到这个逻辑,类似@MapperScan(basePackages = {"com.dao.mapper"},sqlSessionFactoryRef = "dbSqlSessionFactory"),这里的sqlSessionFactoryBeanName就是sqlSessionFactoryRef的值,因为当前是注册bean信息阶段,所以还没有sqlSessionFactory实例,这里只是设置了名字,通过RuntimeBeanReference来告诉后续实例化时,使用sqlSessionFactoryBeanName这个名字来注入sqlSessionFactory
        definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionFactory != null) {
        //目前还没看到哪里能走到这个逻辑
        definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
        explicitFactoryUsed = true;
      }
      
      //设置sqlSessionTemplate,逻辑与sqlSessionFactory类似
      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;
      }

      if (!explicitFactoryUsed) {
        LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        //自动装配的情况下会走到这里,因为前面指定sqlSessionFactory或sqlSessionTemplate的逻辑都不会走,这时候会设置注入的方式为类型注入,注意,这个和Spring注解(@Autowire)注入的方式不是一回事,MapperFactoryBean本身也没有使用任何注解,我们知道MapperFactoryBean有一个sqlSessionTemplate属性需要注入,后续实例化MapperFactoryBean时,在注入ssqlSessionTemplate时,会根据这里设置的类型注入的方式进行注入,这种注入方式会调用setter方法进行注入,也就是会调用org.mybatis.spring.support.SqlSessionDaoSupport#setSqlSessionTemplate这个方法注入,我们知道sqlSessionTemplate在自动配置的情况下,已经默认创建了,所以根据类型找到的就是这个
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }

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

spring种5中注入模型

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

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

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

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

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

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

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

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

boot集成Mysql和mybatis spring mybatis集成到springboot_spring boot_04

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

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

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

this.reader.loadBeanDefinitions(configClasses);

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

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

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

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

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

2.3.2.@MapperScan注册

看下@MapperScan注解的定义。

boot集成Mysql和mybatis spring mybatis集成到springboot_spring boot_05

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

void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {

    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

    // this check is needed in Spring 3.1
    Optional.ofNullable(resourceLoader).ifPresent(scanner::setResourceLoader);

    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<>();
    basePackages.addAll(
        Arrays.stream(annoAttrs.getStringArray("value"))
            .filter(StringUtils::hasText)
            .collect(Collectors.toList()));

    basePackages.addAll(
        Arrays.stream(annoAttrs.getStringArray("basePackages"))
            .filter(StringUtils::hasText)
            .collect(Collectors.toList()));

    basePackages.addAll(
        Arrays.stream(annoAttrs.getClassArray("basePackageClasses"))
            .map(ClassUtils::getPackageName)
            .collect(Collectors.toList()));

    scanner.registerFilters();
    scanner.doScan(StringUtils.toStringArray(basePackages));
  }

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

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

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

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

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

2.3.3.小结

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

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

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

2.4.mapper的创建

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

// Trigger initialization of all non-lazy singleton beans...
        for (String beanName : beanNames) {
            RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
            if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
                if (isFactoryBean(beanName)) {
                    // getBean(beanName)获取的是FactoryBean创建的bean实例
                    // getBean("&"+beanName)获取FactoryBean本身,所以这里获取的是FactoryBean本身
                    Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
                    if (bean instanceof FactoryBean) {
                        final FactoryBean<?> factory = (FactoryBean<?>) bean;
                        boolean isEagerInit;
                        // 判断是否有代码运行权限
                        if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
                            isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
                                            ((SmartFactoryBean<?>) factory)::isEagerInit,
                                    getAccessControlContext());
                        }
                        else {
                            // 默认情况 FactoryBean 延迟创建bean
                            // 但如果是 SmartFactoryBean,而且设置其 eagerInit 值为 true
                            // 那么就进行提前创建
                            isEagerInit = (factory instanceof SmartFactoryBean &&
                                    ((SmartFactoryBean<?>) factory).isEagerInit());
                        }
                        if (isEagerInit) {
                            // 进行提前创建
                            getBean(beanName);
                        }
                    }
                }
                else {
                    getBean(beanName);
                }
            }
        }

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

// Create bean instance.
				if (mbd.isSingleton()) {
					sharedInstance = getSingleton(beanName, () -> {
						try {
							return createBean(beanName, mbd, args);
						}
						catch (BeansException ex) {
							// Explicitly remove instance from singleton cache: It might have been put there
							// eagerly by the creation process, to allow for circular reference resolution.
							// Also remove any beans that received a temporary reference to the bean.
							destroySingleton(beanName);
							throw ex;
						}
					});
					bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}

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

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

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

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

看下MapperFactoryBean的继承体系。

boot集成Mysql和mybatis spring mybatis集成到springboot_sql_06

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

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

        try {
            this.initDao();
        } catch (Exception var2) {
            throw new BeanInitializationException("Initialization of DAO failed", var2);
        }
    }

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

@Override
  protected void checkDaoConfig() {
    //检查sqlSessionTemplate不能为空,这里肯定不会为空,在属性填充阶段已经注入了
    super.checkDaoConfig();
    //检查mapper接口不能为空,这里肯定不会为空,在实例化阶段已经通过有参构造函数传进来了
    notNull(this.mapperInterface, "Property 'mapperInterface' is required");

    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }

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

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

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

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

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

protected Object getObjectForBeanInstance(
      Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {

    // Don't let calling code try to dereference the factory if the bean isn't a factory.
    if (BeanFactoryUtils.isFactoryDereference(name)) {//是否是FactoryBean名字的前缀&
      if (beanInstance instanceof NullBean) {
        return beanInstance;
      }
      if (!(beanInstance instanceof FactoryBean)) {//不是FactoryBean的话名字有&会报异常
        throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass());
      }
      if (mbd != null) {
        mbd.isFactoryBean = true;
      }
      //要找的就是FactoryBean自身,直接返回
      return beanInstance;
    }

    // Now we have the bean instance, which may be a normal bean or a FactoryBean.
    // If it's a FactoryBean, we use it to create a bean instance, unless the
    // caller actually wants a reference to the factory.
    //不是FactoryBean就直接返回,也就是普通的bean
    if (!(beanInstance instanceof FactoryBean)) {
      return beanInstance;
    }

    //除了以上逻辑,那就剩下最后一种情况了,就是获取的是FactoryBean创建的bean
    Object object = null;
    if (mbd != null) {
      mbd.isFactoryBean = true;
    }
    else {
      //从FactoryBean的缓存中获取
      object = getCachedObjectForFactoryBean(beanName);
    }
    if (object == null) {
      // Return bean instance from factory.
      FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
      // Caches object obtained from FactoryBean if it is a singleton.
      //mbd没定义,但是FactoryBean是有定义的,获取mbd
      if (mbd == null && containsBeanDefinition(beanName)) {
        mbd = getMergedLocalBeanDefinition(beanName);
      }
      boolean synthetic = (mbd != null && mbd.isSynthetic());
      object = getObjectFromFactoryBean(factory, beanName, !synthetic);
    }
    return object;
  }

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

2.5.mapper的注入

一般在sercie里使用mapper:

@Autowired
     private MyMapper myMapper;

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

// Initialize the bean instance.
		Object exposedObject = bean;
		try {
			populateBean(beanName, mbd, instanceWrapper);
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
		catch (Throwable ex) {
			if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
				throw (BeanCreationException) ex;
			}
			else {
				throw new BeanCreationException(
						mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
			}
		}

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

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

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

链路如下:

org.mybatis.spring.mapper.MapperFactoryBean#getObject -->
org.mybatis.spring.SqlSessionTemplate#getMapper -->
org.apache.ibatis.session.Configuration#getMapper -->
org.apache.ibatis.binding.MapperRegistry#getMapper

看下这个方法的代码:

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

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

protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

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

2.5.mapper执行方法

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

Food food = myMapper.getById(7L);

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

@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

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

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);
          if (method.returnsOptional() &&
              (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        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;
  }

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

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

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

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

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

3.总结

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

boot集成Mysql和mybatis spring mybatis集成到springboot_sql_07