1、spring-boot项目中启动mybatis

mybatis在提供了mybatis-spring-boot-autoconfigure,用于spring-boot项目中自动加载注入mybatis类,这里采用了springboot指定SPI规范,SPI规范可参考

在mybatis-spring-boot-autoconfigure包的META-INF目录的spring-factories文件中,配置了mybatis的自动配置的类,boot项目在启动的时候会扫描所有jar包下的/META-INF/spring-factories文件,所以可以扫描到该文件资源。

mybatis springboot start版本 mybatis-spring-boot-autoconfigure_spring


mybatis springboot start版本 mybatis-spring-boot-autoconfigure_mybatis_02


key值为org.springframework.boot.autoconfigure.EnableAutoConfiguration,下图是spring-boot jar包下的spring-factories文件,可以看到该文件中也有一个key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的键值对(其中值有很多类名以逗号隔开)。也就是说配置了该key,项目在启动的时候,boot就会进行自动配置


在MybatisAutoConfiguration配置类中有mybatis的核心Bean如SqlSessionFactory等,整个串起来就可以知道,为什么spring-boot项目启动的时候就会注入mybatis各种bean了。

2、mybatis启动过程

以下是MybatisAutoConfiguration配置类中sqlSessionFactory对象的创建过程
@Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        factory.setVfs(SpringBootVFS.class);
        if (StringUtils.hasText(this.properties.getConfigLocation())) {
            factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
        }

        org.apache.ibatis.session.Configuration configuration = this.properties.getConfiguration();
        if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
            configuration = new org.apache.ibatis.session.Configuration();
        }

        if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
            Iterator var4 = this.configurationCustomizers.iterator();

            while(var4.hasNext()) {
                ConfigurationCustomizer customizer = (ConfigurationCustomizer)var4.next();
                customizer.customize(configuration);
            }
        }

        factory.setConfiguration(configuration);
        if (this.properties.getConfigurationProperties() != null) {
            factory.setConfigurationProperties(this.properties.getConfigurationProperties());
        }

        if (!ObjectUtils.isEmpty(this.interceptors)) {
            factory.setPlugins(this.interceptors);
        }

        if (this.databaseIdProvider != null) {
            factory.setDatabaseIdProvider(this.databaseIdProvider);
        }

        if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
            factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
        }

        if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
            factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
        }

        if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
            factory.setMapperLocations(this.properties.resolveMapperLocations());
        }

        return factory.getObject();
    }

可以看到,先new出了SqlSessionFactoryBean,该类实现了FactoryBean,继承关系结构图如下:最后是调用了factory.getObject()

public SqlSessionFactory getObject() throws Exception {
        if (this.sqlSessionFactory == null) {
            this.afterPropertiesSet();
        }

        return this.sqlSessionFactory;
    }

getObject方法中,先判断sqlSessionFactory是否为null,如果为null,会调用afterPropertiesSet创建sqlSessionFactory.

sqlSessionFactory方法总结如下:

  1. dataSource 注入SqlSessionFactoryBean
  2. configLocation注入SqlSessionFactoryBean
  3. 创建configuration, 并注入SqlSessionFactoryBean
  4. MybatisProperties中的configurationProperties注入SqlSessionFactoryBean
  5. 拦截器注入SqlSessionFactoryBean
  6. 解析出properties的mapperLocations注入SqlSessionFactoryBean
  7. buildSqlSessionFactory
  8. xmlConfigBuilder 解析 mybatis-config.xml 配置
  9. xmlMapperBuilder解析mapper配置
  10. 前两步解析的数据都放在了configuration对象中

每个mapper接口会构建成一个个BeanDefinition对象,以备后续spring创建对象使用,我们知道mapper接口在项目启动后都会有代理对象,而代理对象就是通过BeanDefinition生成的。mapper的BeanDefinition创建过程可参考

3 mapperInterface的代理过程分析

每一个mapper接口对应着一个BeanDefinition,BeanDefinition中的class是MapperFactoryBean
在spring创建对象的时候,遇到BeanDefinition的class是FactoryBean的实现的情况下,就会调用实现类的getObject(),这里针对mapper的话,就是调用MapperFactoryBean的getObject()获取mapper对象。

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

在MapperFactoryBean中有一个属性sqlSession,sqlSession对象实际是SqlSessionTemplate

public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (!this.externalSqlSession) {
      this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
    }
  }

而在SqlSessionTemplate类中有一个属性sqlSessionProxy

public class SqlSessionTemplate implements SqlSession, DisposableBean {

  private final SqlSessionFactory sqlSessionFactory;

  private final ExecutorType executorType;

  private final SqlSession sqlSessionProxy;

  private final PersistenceExceptionTranslator exceptionTranslator;

在SqlSessionTemplate的构造函数中,对sqlSessionProxy进行了初始化,而sqlSessionProxy是通过动态代理实现的初始化,对应的InvocationHandler实现类是SqlSessionInterceptor

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

而SqlSessionTemplate的getMapper方法实现了mapperInterface的代理对象的实现过程

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

这里我们可以看到,调用了configure的getMapper,并将SqlSessionTemplate自己的引用传了进去

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

再进去之后,是MapperRegistry的getMapper接口,这里获得了该mapperInterface对应的mapperProxyFactory,并调用了mapperProxyFactory的newInstance方法

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

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

在mapperProxyFactory的newInstance方法中,创建了mapperProxy对象,并通过动态代理创建了该mapperInterface的代理对象。

这里不难猜到,MapperProxy是InvocationHandler的实现类,而在调用mapperInterface方法时,都会调用MapperProxy的invoke方法(动态代理的规范),那么我们来看MapperProxy的invoke方法的代码逻辑

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
//若是Object的方法,那么直接调用该方法执行即可
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }

            if (this.isDefaultMethod(method)) {
                return this.invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
//这里说明了,mapper的每一个自定义函数都对应一个MapperMethod,调用该方法时,就是调用该方法对应的MapperMethod的execute方法
        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        return mapperMethod.execute(this.sqlSession, args);
    }

调用mapperInterface的方法时,会调用mapperMethod.execute(this.sqlSession, args); 这里的sqlSession就是我们上面分析的SqlSessionTemplate,我们看execute内部流程

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

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

在该方法中,我们看到了分INSERT、UPDATE、DELETE、SELECT等不清的情况进行不同的调用,这里我们以INSERT为例分析一下,在INSERT分支中,其实是通过sqlSession的insert实现的,我们再看sqlSession的insert实现(这里其实就是SqlSessionTemplate类的insert方法)

public int insert(String statement, Object parameter) {
    return this.sqlSessionProxy.insert(statement, parameter);
  }

这里,sqlSession的insert其实是委托给了sqlSessionProxy对象的insert方法,前面我们分析过,sqlSessionProxy对象的创建其实是通过动态代理生成的,其InvocationHandler实现类是SqlSessionInterceptor,所以在调用sqlSessionProxy都会走到SqlSessionInterceptor的invoke方法中。

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

在SqlSessionInterceptor的invoke方法中,又重新生成了一个sqlSession,通过这个新生成的sqlSession进行了数据库的操作。

从上面的分析,我们可以总结出,

各对象的引用关系如下(实线箭头代表引用的关系,虚线箭头代表的代理的关系)

mybatis springboot start版本 mybatis-spring-boot-autoconfigure_spring boot_03


service中调用某个mapper接口中的方法(如insert),其内部走的大概流程如下:

mybatis springboot start版本 mybatis-spring-boot-autoconfigure_xml_04