Spring集成Mybatis源码分析

一、Mybatis的启动流程

  Spring能够与Mybatis的完美集成,只要引入mybatis-spring.jar包,都不需要配置mybatis-Config.xml文件,就可以通过Spring的IOC获取到Mybatis的Mapper来进行持久化操作,这篇文章将会从源码级别讲述mybatis与spring的集成机制。

public static void main(String[] args) throws IOException {
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    // 初始化配置, 创建SqlSessionFactory
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    // 创建会话
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // 获取Mapper代理类
    BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
    // 执行用户请求
    Blog blog = blogMapper.selectBlog(1L);
  }

以上面代码为例,在Mybatis启动的过程中,首先会通过加载xml配置,生成SqlSessionFactory,然后当发起Mapper请求时,则通过Mapper代理类中的SqlSession完成一次数据库会话,这个具体过程可参考我的其他Mybatis源码文章,在这个过程中,Mybatis的四大组件都参与了会话,但是他们的生命周期却是不同,如下图:

spring mvc整合Mybatis plus spring整合mybatis源码_sql

  • Configuration 管理Mybatis的全局配置,包括缓存、日志、映射器等,加载之后就不会改变,作用于整个应用
  • SqlSessionFactory 会话工厂,负责创建SqlSession, 作用于整个应用
  • SqlSession 会话,负责和数据库交互,真正实现持久化操作,和Mapper、Connection、事务都是一对一,作用域是一次请求,不支持跨线程使用
  • Mapper 映射器,映射用户调用的接口,内部维护一个SqlSession,使用SqlSession实现持久化操作,因为是通过SqlSession的getMapper使用动态代理创建,所以作用域和会话相同,也不支持跨线程。

通过上面的描述,可以发现Mybatis的Mapper的使用并不符合Spring中使用的条件,在Spring中,需要将Mapper作为Bean注入到Service中,实现重复使用,上面和SqlSession绑定的Mapper显然不符合需求

二、集成机制原理

@Configuration
public class MybatisConfig {

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(SqlSessionFactory sqlSessionFactory) {
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        mapperScannerConfigurer.setBasePackage("com.example.dao");
        return mapperScannerConfigurer;
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean;
    }

    @Bean
    public DataSource dataSource() {
        DataSource dataSource = new DriverManagerDataSource("jdbc:mysql://192.168.117.128:3306/pattern?characterEncoding=UTF-8&serverTimeZone=GMT", "root", "root");
        return dataSource;
    }

    public static void main(String[] args) {
        // 初始化Spring容器
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MybatisConfig.class);
        // 获取Mapper代理类
        BlogMapper blogMapper = applicationContext.getBean("blogMapper", BlogMapper.class);
        // 执行用户请求
        Blog one = blogMapper.findOne(1L);
        System.out.println(one);
    }
}

上面是Spring集成Mybatis的代码配置,配置中初始化了两个Bean,分别是MapperScannerConfigurer和SqlSessionFactoryBean,MapperScannerConfigurer类的作用扫描Mapper包下的Mapper接口,并将他们的Bean信息保存IOC中。SqlSessionFactoryBean类用于加载和初始化Mybatis的配置,完成Configuration、SqlSessionFactory的初始化。

spring mvc整合Mybatis plus spring整合mybatis源码_初始化_02

如上图,就是集成简单流程,下面将从包扫描、Mybatis配置初始化、请求过程三个部分分析集成的源码。

Mapper包扫描

spring mvc整合Mybatis plus spring整合mybatis源码_初始化_03

Mapper包扫描的流程如上图,MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor,以postProcessBeanDefinitionRegistry()方法为入口,开始包扫描,源码如下:

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        if (this.processPropertyPlaceHolders) {
          processPropertyPlaceHolders();
        }
        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
        // ...
        // 扫描包
        scanner.scan(
            StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
      }

之后,会通过ClassPathMapperScanner的doScan方法获取BeanDefinitionHolder,BeanDefinitionHolder封装了Mapper代理类的name和描述,之后会调用processBeanDefinitions()处理BeanDefinitionHolder,将获取BeanDefinitionHolder中的BeanDefinition,并封装为MapperFactoryBean的BeanDefinition注册到IOC中, 到此,就完成了整个包扫描流程,源码如下:

public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    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 {
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions; 
  }
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    AbstractBeanDefinition definition;
    BeanDefinitionRegistry registry = getRegistry();
    // 遍历beanDefinitions
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (AbstractBeanDefinition) holder.getBeanDefinition();
      // ...
      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean  真实的Bean类型为MapperFactoryBean
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); 
      definition.setBeanClass(this.mapperFactoryBeanClass);
      // ...    
      if (!definition.isSingleton()) {
        BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true);
        if (registry.containsBeanDefinition(proxyHolder.getBeanName())) {
          registry.removeBeanDefinition(proxyHolder.getBeanName());
        }
        // 注册Bean到IOC
        registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition());
      }

    }
}

Mybatis配置初始化

spring mvc整合Mybatis plus spring整合mybatis源码_sql_04

如上图,包扫描完成之后,就是SqlSessionFactoryBean的初始化,SqlSessionFactoryBean实现了InitializingBean接口实现了afterPropertiesSet方法,在该方法调用 buildSqlSessionFactory()方法,首先初始化了Configuration,之后使用SqlSessionFactoryBuilder创建SqlSessionFactory,这个SqlSessionFactory之后会注入到SqlSessionTemplate中,并在SqlSessionTemplate中创建SqlSession,这里Configuration和SqlSessionFactory都是Mybatis的组件,所以这里首先完成了Mybatis的配置集成。

Mapper请求过程

 集成之后的Mapper请求要比原生Mybatis的Mapper复杂很多,Mapper也是通过Spring IOC获取,前面已经说过IOC存储的是MapperFactoryBean,所以Mapper代理类的也是通过MapperFactoryBean创建,所以代码分析也从这里开始。

 

spring mvc整合Mybatis plus spring整合mybatis源码_初始化_05

如图,MapperFactoryBean实现了FactoryBean和SqlSessionDaoSupport接口,FactoryBean接口用于Spring中创建复杂Bean,SqlSessionDaoSupport抽象类持有一个SqlSessionTemplate,这个SqlSessionTemplate之后会被注入到Mapper代理类中, MapperFactoryBean创建Mapper代理类时,并不是使用Spring代理创建,而是委托给Mybatis的Configuration组件创建,这里Mapper的获取和原生Mybatis相同,不同是注入代理类的SqlSession不再是DefaultSqlSession对象,而是SqlSessionTemplate,在SqlSessionTemplate中又持有一个SqlSession的代理类.

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  private Class<T> mapperInterface;
    
  // ...
  // 获取Mapper  
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }
  // ...
}
public abstract class SqlSessionDaoSupport extends DaoSupport {

  private SqlSessionTemplate sqlSessionTemplate;
  // ...
  // 注入sqlSessionFactory到SqlSessionTemplate中
  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
      this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
    }
  }
  // ...
  protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    return new SqlSessionTemplate(sqlSessionFactory);
  }
  // ...
  // 获取SqlSessionTemplate    
  public SqlSession getSqlSession() {
    return this.sqlSessionTemplate;
  }
  // ...
}
public class SqlSessionTemplate implements SqlSession, DisposableBean {

  private final SqlSessionFactory sqlSessionFactory;

  private final ExecutorType executorType;

  private final SqlSession sqlSessionProxy;
  
  // ... 
  // 从Configuration中获取Mapper
  @Override
  public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
  }
  // ... 
  @Override
  public Configuration getConfiguration() {
    return this.sqlSessionFactory.getConfiguration();
  }
  // ... 
}

根据上面可以代码可知,SqlSessionTemplate是集成实现的关键,所以下面来分析SqlSessionTemplate的代码,首先SqlSessionTemplate中持有一个SqlSession的代理类,也就是sqlSessionProxy对象,SqlSessionTemplate的增删改查都是通过这个代理类实现.同时可以发现不支持commit等事务方法,由此可以推断,集成之后,Mybatis不在管理事务,而是交给Spring管理.

public class SqlSessionTemplate implements SqlSession, DisposableBean {

  private final SqlSessionFactory sqlSessionFactory;

  private final ExecutorType executorType;

  private final SqlSession sqlSessionProxy;
  
  // ... 
  // 从Configuration中获取Mapper
  @Override
  public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
  }
  // ... 
  @Override
  public Configuration getConfiguration() {
    return this.sqlSessionFactory.getConfiguration();
  }
  // ... 
  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;
    // 创建sqlSessionProxy代理类
    this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class }, new SqlSessionInterceptor());
  }
  // ...
  // 通过sqlSessionProxy代理类实现数据库操作
  @Override
  public <T> T selectOne(String statement, Object parameter) {
    return this.sqlSessionProxy.selectOne(statement, parameter);
  }
  @Override
  public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
    return this.sqlSessionProxy.selectMap(statement, mapKey);
  }
  // ...
  // Spring事务无法被覆盖
  @Override
  public void commit(boolean force) {
    throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
  }
  @Override
  public void rollback() {
    throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
  }
  // ...
}

之后最重要的一个点就是SqlSessionInterceptor类,SqlSessionInterceptor类InvocationHandler接口,实现了invoke方法,这个方法中记录了事务处理以及集成Mybatis一级缓存失效的原因.

private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      // 创建一个DefaultSqlSession对象    
      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)) {
          // 判断SqlSession有没有被Spring事务管理
          // 如果被管理,强制提交
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator
              .translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }    
        }
        throw unwrapped;
      } finally {
        // 关闭DefaultSqlSession对象
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

可以发现在invoke方法中,创建了DefaultSqlSession执行数据库操作,同时,Mybatis事务交由Spring管理,这也是之前commit方法不支持使用的原因,另外就是每次操作都会关闭DefaultSqlSession对象,因为Mybatis的一级缓存是SqlSession域缓存,所以这也导致一级缓存失效.