mybatisソースコード分析06:Springとの統合

注:このシリーズのソースコード分析はmybatis-spring 2.0.6に基づいており、対応するバージョンのmybatisは3.5.6であり、ソースコードのgiteeウェアハウスのアドレスはfuncy/mybatis-springです。

についてmybatisは、ほとんどのspring場合、コンテナと組み合わせて使用​​します。この記事では、これmybatisとコンテナspringの統合プロセスを分析します。

1.ソースコードを入手して準備するdemo

1.1mybatisおよびspring統合プロジェクトとソースコード

mybatisspring統合プロジェクトはと呼ばれmybatis-springソースコードはgithubでホストされています。もちろん、私もそれを自分のgiteeにプルして作成しました。アドレスは、funcy / mybatisです。ソースコードcheckoutpull操作については、焦点ではありません。この記事の、それで私はこれ以上は言いません。

以前に分析されたmybatisバージョン3.5.6は、ここで分析したバージョンはであり、それが参照しているmybatisのmybatis-springバージョンは正確にです。2.0.63.5.6

1.2デモの準備

ソースコードを入手したら、デモの例src/testを作成できます。以下で作成します。ディレクトリ全体は次のとおりです。

コードディレクトリ:

mapper.xmlファイル:

  1. 準備するmodel//mapperservice
/**
 * User.java
 */
public class User implements Serializable {

  /**
   * 用户id
   */
  private Long id;

  /**
   * 登录名
   */
  private String loginName;

  /**
   * 用户昵称
   */
  private String nick;

  ... 省略set/get方法
}

/**
 * UserMapper.java
 */
 public interface UserMapper {

  /**
   * 查询
   * 
   * @return
   */
  List<User> selectList(@Param("id") Long id, @Param("limit") Integer limit);

  /**
   * 更新
   * 
   * @param id
   * @param nick
   * @return
   */
  int updateUser(@Param("id") Long id, @Param("nick") String nick);
}

/**
 * UserService.java
 */
 @Service
public class UserService {

  @Autowired
  private UserMapper userMapper;

  /**
   * 获取列表
   * 
   * @param id
   * @param limit
   * @return
   */
  public List<User> getUser(Long id, Integer limit) {
    return userMapper.selectList(id, limit);
  }
}
复制代码

UserService.java春によって管理され、春によって注入されuserMapperます。

  1. UserMapper.xml
<?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="org.mybatis.spring.demo.mapper.UserMapper">
  <select id="selectList" resultType="org.mybatis.spring.demo.model.User">
    select id, login_name as loginName, nick from user
    <trim prefix="where" prefixOverrides="AND |OR">
      <if test="id != null">
        and id = #{id}
      </if>
    </trim>
    <if test="limit != null">
      limit #{limit}
    </if>
  </select>

  <update id="updateUser">
    update user set nick = #{nick} where id = #{id}
  </update>
</mapper>
复制代码
  1. 構成クラスを準備します
@Configuration
public class Demo01Config {

  @Bean
  public SqlSessionFactory sqlSessionFactory() throws Exception {
    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    // 设置数据源
    factoryBean.setDataSource(dataSource());
    // mapper.xml 文件路径
    Resource[] resources = new PathMatchingResourcePatternResolver()
      .getResources("classpath*:/mapper/**/*.xml");
    factoryBean.setMapperLocations(resources);
    return factoryBean.getObject();
  }

  @Bean
  public DataSource dataSource() throws Exception {
    Driver driver = new com.mysql.cj.jdbc.Driver();
    String url = "jdbc:mysql://localhost:3306/test";
    String username = "root";
    String password = "123";
    return new SimpleDriverDataSource(driver, url, username, password);
  }
}
复制代码
  1. メインクラスを準備する

最後にメインクラス:

@ComponentScan
@MapperScan("org.mybatis.spring.demo.mapper")
public class Demo01Main {

  public static void main(String[] args) {
    ApplicationContext application 
        = new AnnotationConfigApplicationContext(Demo01Main.class);
    // 获取 userService 对象
    UserService userService = (UserService) application.getBean("userService");
    List<User> list = userService.getUser(3L, 10);
    System.out.println(list);
  }

}
复制代码

実行すると、結果は次のようになります。

[User{id=3, loginName='test', nick='test'}]
复制代码

mybatisとspringの統合が成功していることがわかります。次に、mybatis-springこのデモに従ってプロジェクトを分析します。

2.SqlSessionFactoryBeanクラス

我们先来看看Demo01Config引入的Bean

  @Bean
  public SqlSessionFactory sqlSessionFactory() throws Exception {
    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    // 设置数据源
    factoryBean.setDataSource(dataSource());
    // mapper.xml 文件路径
    Resource[] resources = new PathMatchingResourcePatternResolver()
      .getResources("classpath*:/mapper/**/*.xml");
    factoryBean.setMapperLocations(resources);
    return factoryBean.getObject();
  }
复制代码

这个方法中,先是创建了SqlSessionFactoryBean,然后配置了数据源以及mapper.xml文件路径,然后调用SqlSessionFactory#getObject()方法获取SqlSessionFactory,看来SqlSessionFactoryBean还是挺关键的,我们看看它的定义:

/**
 * 实现了 FactoryBean
 */
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, 
        InitializingBean, ApplicationListener<ApplicationEvent> {
    ...
}
复制代码

可以看到,SqlSessionFactoryBean实现了FactoryBean,其中类为SqlSessionFactory,这样调用SqlSessionFactory#getObject()方法获取SqlSessionFactory了。

FactoryBean是spring中一类重要的bean,它可以将普通的类包装成FactoryBean注册到spring容器中,要获取该类的bean时,只需调用SqlSessionFactory#getObject()方法即可。

SqlSessionFactorymybatis中的重要类,用来生成SqlSession,而SqlSession正是用来获取XxxMapper实例、执行数据库操作的类。

SqlSessionFactoryBean可以定义很多很多的配置,它的setXxx方法如下:

实际上,在mybatis-config.xml配置文件中配置的内容都可以在这里找到对应的setXxx方法,示例demo中,只是设置了数据源与mapper.xml的文件路径。

了解了SqlSessionFactoryBean的相关功能后,我们来看看SqlSessionFactoryBean#getObject方法:

  public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      // 主动调用 afterPropertiesSet()
      afterPropertiesSet();
    }

    return this.sqlSessionFactory;
  }

  /**
   * 重写了 InitializingBean 的方法
   */
  public void afterPropertiesSet() throws Exception {
    ...
    // 这里构建了 sqlSessionFactory
    this.sqlSessionFactory = buildSqlSessionFactory();
  }

复制代码

我们继续进入buildSqlSessionFactory()方法:

protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

    final Configuration targetConfiguration;

    // 这里省略了好多好多的代码,都是处理 targetConfiguration 的配置
    ...

    // 处理数据源
    targetConfiguration.setEnvironment(new Environment(this.environment,
        this.transactionFactory == null ? new SpringManagedTransactionFactory() 
            : this.transactionFactory, this.dataSource));

    if (this.mapperLocations != null) {
      if (this.mapperLocations.length == 0) {
        LOGGER.warn(() -> "...");
      } else {
        for (Resource mapperLocation : this.mapperLocations) {
          if (mapperLocation == null) {
            continue;
          }
          try {
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(
                mapperLocation.getInputStream(), targetConfiguration, 
                mapperLocation.toString(), targetConfiguration.getSqlFragments());
            // 在这里解析 mapper.xml 文件
            xmlMapperBuilder.parse();
          } catch (Exception e) {
            throw new NestedIOException(...);
          } finally {
            ErrorContext.instance().reset();
          }
          LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
    }

    return this.sqlSessionFactoryBuilder.build(targetConfiguration);
  }
复制代码

首先,这个方法非常长,以上代码是精简后的代码,省略的是大段大段的设置targetConfiguration属性的代码,剩下的是两个功能:

  1. 设置数据源
  2. 解析xml

这两个属性也是我们在sqlSessionFactory()中设置的。

处理完sqlSessionFactory()方法后,sqlSessionFactory 就注册到spring容器中了。

3. @MapperScan扫描流程

sqlSessionFactory注册到spring容器并不能实现MapperInterface的自动注入,像这样:

@Autowired
private UserMapper userMapper;
复制代码

这个功能的实现,就是@MapperScan的功劳了。

Demo01Main.java中,我们是这样配置@MapperScan注解的:

@ComponentScan
@MapperScan("org.mybatis.spring.demo.mapper")
public class Demo01Main {
    ...
}
复制代码

这里的org.mybatis.spring.demo.mapper就是MapperInterface的包路径了,也是mybatis要扫描的包路径.

我们进入@MapperScan:

...
// @Import 里的类是关键
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
    ...
}
复制代码

MapperScan中,有一个@Import标签,它引入了一个类:MapperScannerRegistrar,我们来看看它做了啥。

3.1 MapperScannerRegistrar

MapperScannerRegistrar的代码如下:

/**
 * 实现了 ImportBeanDefinitionRegistrar类
 */
public class MapperScannerRegistrar 
    implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

  ...

  /**
   * 来自于 ImportBeanDefinitionRegistrar 的方法,可以往spring容器中注册beanDefinition
   */
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, 
        BeanDefinitionRegistry registry) {
    // 获取 MapperScan 的属性值
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
      // 注册mapper接口类
      registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
          generateBaseBeanName(importingClassMetadata, 0));
    }
  }

  ...
}
复制代码

MapperScannerRegistrar 实现了ImportBeanDefinitionRegistrar,在其registerBeanDefinitions中向spring容器注入了某些beanDefinition,我们继续进入registerBeanDefinitions(...)方法,看看注入了啥:

void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
      BeanDefinitionRegistry registry, String beanName) {

    // 创建 BeanDefinitionBuilder,构建 MapperScannerConfigurer
    BeanDefinitionBuilder builder = BeanDefinitionBuilder
        .genericBeanDefinition(MapperScannerConfigurer.class);
    builder.addPropertyValue("processPropertyPlaceHolders", true);

    // 以下为解析 @MapperScan 的各种属性,并它他们设置到 builder 中
    ...
    // 解析扫描的包路径
    List<String> basePackages = new ArrayList<>();
    basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("value"))
        .filter(StringUtils::hasText).collect(Collectors.toList()));

    ...
    // 添加属性值
    builder.addPropertyValue("basePackage", 
            StringUtils.collectionToCommaDelimitedString(basePackages));

    // 注册 MapperScannerConfigurer 为 beanDefinition
    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

  }
复制代码

registerBeanDefinitions(...)方法先是为MapperScannerConfigurer创建了一个BeanDefinitionBuilder,然后就是解析@MapperScan的各种属性,并添加到BeanDefinitionBuilder中,然后调用registry.registerBeanDefinition容器中。上述方法是精简后的代码,由于@MapperScan支持的配置比较多,原本的代码非常长,精简后的代码中仅留下了我们设置的value值,它就是这样设置到BeanDefinitionBuilder中的。

因此,整个MapperScannerRegistrar就只是向spring容器中注册了一个类:MapperScannerConfigurer

3.2 MapperScannerConfigurer

接下来我们就把目光指向了MapperScannerConfigurer

/** 
 * 它实现了 BeanDefinitionRegistryPostProcessor
 */ 
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, 
    InitializingBean, ApplicationContextAware, BeanNameAware {

  /**
   * 来自于 BeanDefinitionRegistryPostProcessor 的方法
   * 确切地说,是来自于 BeanFactoryPostProcessor 的方法,因为 BeanFactoryPostProcessor 是
   * BeanDefinitionRegistryPostProcessor 的父类
   */
  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    // 方法实现为空,没有任何内容,所以spring为何不在接口方法中加个default关键字呢
  }

  /**
   * 来自于 BeanDefinitionRegistryPostProcessor 的方法
   */
  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }

    // 定义扫描类
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    // 设置各种属性,一些是由 @MapperScan 中解析得到的
    scanner.setAddToConfig(this.addToConfig);
    scanner.setAnnotationClass(this.annotationClass);
    scanner.setMarkerInterface(this.markerInterface);
    scanner.setSqlSessionFactory(this.sqlSessionFactory);
    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
    scanner.setResourceLoader(this.applicationContext);
    scanner.setBeanNameGenerator(this.nameGenerator);
    scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
    if (StringUtils.hasText(lazyInitialization)) {
      scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
    }
    if (StringUtils.hasText(defaultScope)) {
      scanner.setDefaultScope(defaultScope);
    }
    // 注册过滤配置
    scanner.registerFilters();
    // 在这里处理扫描操作
    scanner.scan(
        // 这里就是前面解析的 basePackage
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

}
复制代码

MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor,这是spring提供的BeanFactoryPostProcessor之一,可以在spring执行的早期向容器中注册beanDefinition,它提供了两个方法:postProcessBeanFactory(...)(定制化beanFactory的某些行为)与postProcessBeanDefinitionRegistry(...)(处理beanDefinition注册),由于postProcessBeanFactory(...)方法为空实现,因此我们重点关注postProcessBeanDefinitionRegistry(...)方法。

postProcessBeanDefinitionRegistry(...)方法中,先是创建了一个ClassPathMapperScanner的实例,该实例主要功能就是用来处理MapperInterface的扫描功能的,之后再对ClassPathMapperScanner实例进行一些属性设置,这些属性大部分来自于@@MapperScan的设置,最后调用ClassPathMapperScanner#scan(...)方法也进行扫描操作,在这个方法里,满足条件的MapperInterface会被转化为 BeanDefinition从而注册到spring容器中的。

接下来我们就重点关注ClassPathMapperScanner

3.3 ClassPathMapperScanner

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
    ...
}
复制代码

可以看到,ClassPathMapperScanner继承了ClassPathBeanDefinitionScanner,关于ClassPathBeanDefinitionScanner,熟悉spring的小伙伴并不陌生,它就是spring中用来处理@ComponentScan注解扫描的类,也就是说,处理@MapperScan的扫描底层使用的也是与@ComponentScan一样的方式。

1. ClassPathMapperScanner#registerFilters(...)

我们知道,spring的@ComponentScan只扫描包下标记了@Component注解的类,而MapperInterface只是个接口,它为何也能扫描呢?这就是ClassPathMapperScanner#registerFilters(...)的功能了,在MapperScannerConfigurer#postProcessBeanDefinitionRegistry中我们也看到了它的调用:

  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    // 省略一些配置  
    ...

    // 注册过滤配置
    scanner.registerFilters();
    // 在这里处理扫描操作
    scanner.scan(
        // 这里就是前面解析的 basePackage
        StringUtils.tokenizeToStringArray(this.basePackage, 
            ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }
复制代码

我们来看看ClassPathMapperScanner的注册过滤配置的方法registerFilters

  public void registerFilters() {
    boolean acceptAllInterfaces = true;

    // 省略一些配置
    ...

    // 表示包下的接口也扫描
    if (acceptAllInterfaces) {
      addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
    }

    // 过滤 package-info.java 文件
    addExcludeFilter((metadataReader, metadataReaderFactory) -> {
      String className = metadataReader.getClassMetadata().getClassName();
      return className.endsWith("package-info");
    });
  }

复制代码

这个方法里注册了一些过滤配置,这里我们只关注acceptAllInterfaces的处理,相关代码为:

if (acceptAllInterfaces) {
  addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
}
复制代码

这表明包下的一切类、接口等都会被扫描为BeanDefinition

2.ClassPathMapperScanner#doScan

准备工作做完后,我们来看看具体的扫描流程,也就是scanner.scan(...)方法,ClassPathMapperScanner并没有重写该方法,调用的 是spring 提供的 ClassPathBeanDefinitionScanner#scan方法中:

public int scan(String... basePackages) {
    int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
    // 这是真正干活的方法
    doScan(basePackages);

    // Register annotation config processors, if necessary.
    if (this.includeAnnotationConfig) {
        AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    }

    return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
复制代码

doScan(...)方法ClassPathMapperScanner是有重写的,我们进入ClassPathMapperScanner#doScan方法:

  @Override
  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 {
      // 处理扫描得到的 beanDefinitions
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }
复制代码

doScan(...)方法继续调用父类的doScan(...)方法进行扫描,然后再调用processBeanDefinitions(...)处理扫描结果,调用super.doScan(basePackages)得到的结果如下:

org.mybatis.spring.demo.mapper包下的UserMapper已经被扫描为beanDefinition了,它的beanClassUserMapper,这是个接口,它该如何进行实例化呢?这就是processBeanDefinitions(...)的功能,我们继续看下去。

3. ClassPathMapperScanner#processBeanDefinitions

ClassPathMapperScanner#processBeanDefinitions方法的代码如下:

  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    AbstractBeanDefinition definition;
    BeanDefinitionRegistry registry = getRegistry();
    // 遍历得到的 beanDefinitions
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (AbstractBeanDefinition) holder.getBeanDefinition();

      ...
      // 设置构造方法参数
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
      // 获取 beanClassName
      String beanClassName = definition.getBeanClassName();

      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
      // 修改 beanClass 为 MapperFactoryBean
      definition.setBeanClass(this.mapperFactoryBeanClass);

      // 设置 MapperFactoryBean 的 factoryBeanObjectType 为 beanClassName
      definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClassName);

      boolean explicitFactoryUsed = false;
      // 设置 sqlSessionFactory,这里的 sqlSessionFactoryBeanName 与 sqlSessionFactory 都是
      // 由 @MapperScan 中配置的,如果未配置,这两个属性的值就为 null
      if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
        // 设置 sqlSessionFactory 属性,设置时会调用 setSqlSessionFactory(...) 方法
        definition.getPropertyValues().add("sqlSessionFactory",
            // RuntimeBeanReference 会把传入的字符串解析为 bean
            new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionFactory != null) {
        // 如果传入的是 sqlSessionFactory,则直接设置值
        definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
        explicitFactoryUsed = true;
      }

      // 设置 sqlSessionTemplate,操作同设置sqlSessionFactory类似
      // 同样地,这里的 sqlSessionTemplateBeanName 与 sqlSessionTemplate 都是
      // 由 @MapperScan 中配置的,如果未配置,这两个属性的值就为 null
      if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
        // 设置 sqlSessionTemplate,这是个beanName,RuntimeBeanReference会beanName解析成对应的bean
        definition.getPropertyValues().add("sqlSessionTemplate",
            new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionTemplate != null) {
        // 设置 sqlSessionTemplate,直接设置 sqlSessionTemplate 的值
        definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
        explicitFactoryUsed = true;
      }

      if (!explicitFactoryUsed) {
        // 如果 sqlSessionFactory与sqlSessionTemplate都未设置,就使用byType的自动注入方式
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
      
      // 省略了其他代码
      ...

    }
  }
复制代码

这个方法还是有点长的,经过精简后,代码还是比较清晰的,这里先来看看它是如何替换beanDefinition的beanClass的:

  1. 调用definition.getBeanClassName()获取原本的beanClassName,这里得到的beanClassNameorg.mybatis.spring.demo.mapper.UserMapper
  2. 设置构造方法参数为beanClassName,也就是org.mybatis.spring.demo.mapper.UserMapper
  3. definitionbeanClass设置为org.mybatis.spring.mapper.MapperFactoryBean

经过这三步之后,definitionbeanClass就是MapperFactoryBean了,就可以放心大胆地创建MapperFactoryBean实例了:

我们要的是org.mybatis.spring.demo.mapper.UserMapper,但spring给我们创建的却是org.mybatis.spring.mapper.MapperFactoryBean,这两者之间有何关系呢?我们进入MapperFactoryBean一探究竟:

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

  private Class<T> mapperInterface;

  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

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

  ...

}
复制代码

从代码来看,它继承了FactoryBean,类型为mapperInterface,也就是我们处理构造方法传入的类型:

definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
复制代码

这样一来,相当于MapperFactoryBeanmapperInterface值为org.mybatis.spring.demo.mapper.UserMapper.

除了构造方法外,ClassPathMapperScanner#processBeanDefinitions方法还为MapperFactoryBean设置了其他的一些值,这里我们主要关注sqlSessionFactorysqlSessionTemplate的设置。

先看sqlSessionFactory的设置,如果@MapperScan中配置了sqlSessionFactoryBeanName,就会将其设置到MapperFactoryBeansqlSessionFactory属性中。如果sqlSessionFactoryBeanName未配置,就看@MapperScan有没有配置sqlSessionFactory,如果配置了,就直接设置。

处理sqlSessionFactoryBeanName设置操作时,有两点需要注意:

  1. 传入的sqlSessionFactoryBeanName是字符串,如果转换为spring bean呢?这就是RuntimeBeanReference的功能了,它会字符串解析为具体的spring bean
  2. MapperFactoryBean并没有sqlSessionFactory的属性,不仅如此,就边它的父类也没有,它该如何设置呢?实际上,在spring中设置属性值,并不需要属性存在,只需要属性对应的setXxx(...)方法存在就可以了,而MapperFactoryBean的父类SqlSessionDaoSupport正好有setSqlSessionFactory(...)方法,因此设置sqlSessionFactory属性时,调用的是setSqlSessionFactory(...)方法。

关于sqlSessionTemplate的设置同sqlSessionFactory非常类似,这里就不再分析了。

在我们的demo中,@MapperScan并没有配置sqlSessionFactoryBeanName/sqlSessionFactory/sqlSessionTemplateName/sqlSessionTemplateMapperFactoryBeansqlSessionFactory是从何而来的呢?

注意看processBeanDefinitions方法,如果sqlSessionFactorysqlSessionTemplateexplicitFactoryUsed的值就为false,接下来会有一个关键操作:

definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
复制代码

这个设置表明,该beanDefinition的注入模式为byType,也就是按类型注入,spring在处理注入时,会搜索MapperFactoryBeansetXxx(),为其设置值。

在我们的示例demo中,MapperFactoryBeansetSqlSessionFactory(...)方法如下:

  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (this.sqlSessionTemplate == null || sqlSessionFactory 
        != this.sqlSessionTemplate.getSqlSessionFactory()) {
      this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
    }
  }
复制代码

spring 会先获取到setSqlSessionFactory(...)方法,然后获知其参数类型为SqlSessionFactory,接着就从spring容器中找到SqlSessionFactory类型的bean,这个bean我们在Demo01Config已经创建了:

@Configuration
public class Demo01Config {

  @Bean
  public SqlSessionFactory sqlSessionFactory() throws Exception {
    ...
  }

  ...
}
复制代码

找到SqlSessionFactory类型的bean后,就以此bean为参数,调用setSqlSessionFactory(...)方法。至于sqlSessionFactory如何设置到MapperFactoryBean的具体操作,那就是setSqlSessionFactory(...)的操作了。

一句话,spring 提供sqlSessionFactory参数,setSqlSessionFactory(...)负责具体的设置操作。

4. 获取注入的XxxMapperMapperFactoryBean#getObject

MapperInterface注入到spring容器的bean是MapperFactoryBean,在自动注入时,该如何拿到对应的MapperInterface bean 呢?实际上,spring 在处理FactoryBean的自动注入时,会调用FactoryBean#getObject获取FactoryBean实际表示的bean,我们进入MapperFactoryBeangetObject()方法:

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

这个方法分为两部分:getSession()getMapper(),我们先来看getSession()方法,它位于SqlSessionDaoSupport内:

  public SqlSession getSqlSession() {
    return this.sqlSessionTemplate;
  }
复制代码

这个方法只是简单地返回了sqlSessionTemplate。在前面我们并没有设置sqlSessionTemplate,这个sqlSessionTemplate是从何而来呢?通过调试,我们发现它是从setSqlSessionFactory(...)中来的:

public abstract class SqlSessionDaoSupport extends DaoSupport {

  private SqlSessionTemplate sqlSessionTemplate;

  /**
   * 设置 SqlSessionFactory
   */
  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (this.sqlSessionTemplate == null 
            || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
      this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
    }
  }

  /**
   * 创建 SqlSessionTemplate
   */
  protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    return new SqlSessionTemplate(sqlSessionFactory);
  }

  ...
}
复制代码

在调用setSqlSessionFactory(...)方法时,会创建sqlSessionTemplate。我们再进入SqlSessionTemplate方法,看看它的处理逻辑:

public class SqlSessionTemplate implements SqlSession, DisposableBean {

  /** 传入的的 sqlSessionFactory */
  private final SqlSessionFactory sqlSessionFactory;

  /** 这就是 sqlSessionProxy */
  private final SqlSession sqlSessionProxy;

  /**
   * SqlSessionTemplate 的构造方法
   */
  public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {
    // 传入的 sqlSessionFactory
    this.sqlSessionFactory = sqlSessionFactory;
    // 又是一个动态代理对象,代理的是 SqlSession 对象
    this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class }, new SqlSessionInterceptor());
    ...
  }

  ...

}
复制代码

SqlSessionTemplate 中在两个重要的成员变量:sqlSessionFactorysqlSessionProxy,这个sqlSessionFactory就是在SqlSessionDaoSupport#setSqlSessionFactory(...)中传入的sqlSessionFactory,而sqlSessionProxySqlSession的代理对象,它的InvocationHandler实例为SqlSessionInterceptor,关于它的妙用我们后面再分析。

回到MapperFactoryBean#getObject()方法,看完了getSession()后,我们再来看看getMapper的逻辑:

public class SqlSessionTemplate implements SqlSession, DisposableBean {

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

  ...
}
复制代码

这个方法比较简单,就是先获取Configuration,然后从Configuration中获取UserMapper的实例,这里的typeUserMapper.classthisSqlSessionTemplate的实例。继续跟进去,到达MapperRegistry#getMapper方法:

    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 {
                // 创建对就的 type 实例
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }
复制代码

可以看到,XxxMapper实例是在调用MapperRegistry#getMapper方法时创建的,关于创建流程及mapperProxyFactory,这些是mybatis相关的内容,本文主要关注与spring的整合,这些就不分析了。

经过了以上步骤,MapperFactoryBean#getObject就能成功获取UserMapper实例了,UserService中的UserMapper也能注入成功了。

4. 执行数据库操作

接下来我们来看看数据库的执行操作,也就是userMapper.selectList(...)方法。

关于mybatisXxxMapper的执行流程,在mybatis源码分析02:执行sql语句一文已经详细分析过了,本文不再赘述,本节我们主要关注SqlSessionTemplate的相关操作。

在前面的分析文章中,我们提到mybatis提供的sqlSession默认实现DefaultSqlSession不是线程安全的,在前面得到的UserMapper中,使用的是SqlSessionTemplate,它是线程安全的吗?我们通过调试来进行分析。

userMapper.selectList(...)的调用过程中,经过一系列的调用栈后,最终会来到MapperMethod#executeForMany方法:

MapperMethod#executeForMany方法中,又会调用sqlSession.selectList(...)方法来获取数据,而这个sqlSession就是SqlSessionTemplate

再进入SqlSessionTemplate#selectList(...)方法:

  public <E> List<E> selectList(String statement, Object parameter) {
    return this.sqlSessionProxy.selectList(statement, parameter);
  }
复制代码

它是调用sqlSessionProxy对象的selectList方法进行查询的。对于sqlSessionProxy,上一节在分析SqlSessionTemplate的构造方法时我们提到,它是在SqlSessionTemplate中创建的:

// 又是一个动态代理对象,代理的是 SqlSession 对象
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
    new Class[] { SqlSession.class }, new SqlSessionInterceptor());
复制代码

没错,它是个动态代理对象,它的InvocationHandler实例为SqlSessionInterceptor,执行sqlSessionProxy.selectList(...)时,SqlSessionInterceptor#invoke(...)会先被执行,我们进入SqlSessionInterceptor#invoke(...)方法:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  // 获取 sqlSession,获取到的类型是 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.commit(true);
    }
    return result;
  } catch (Throwable t) {
    ...
  } finally {
    ...
  }
}
复制代码

这个方法里,主要是处理sqlSession的获取操作,得到sqlSession之后会再调用原本方法。这个获取到的sqlSession是不是线程安全的呢?从运行结果来看,它的实际类型依旧是DefaultSqlSession,并不是线程安全的。那这个操作依然不是线程安全的吗?不一定,衡量DefaultSqlSession是否线程安全,并不一定它本身是否线程安全,如果每个线程都有自己的DefaultSqlSessionDefaultSqlSession不会被跨线程共享,那么即使DefaultSqlSession非线程安全,但整个操作还是线程安全的。

每个线程都有自己的DefaultSqlSession的办法或许你已经想到了,就是ThreadLocalmbyatis提供的SqlSessionManager就是这样实现的,而SqlSessionTemplate由于是与spring结合,这块功能它托管给了spring来做,我们且看sqlSession的获取:

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

    // 获取 SqlSessionHolder,托管给了 spring
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager
        .getResource(sessionFactory);

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

    // 新建一个 sqlSession
    session = sessionFactory.openSession(executorType);
    // 注册SessionHolder
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
  }
复制代码

从代码来看,SqlSession是由TransactionSynchronizationManager.getResource(...)获取的,获取不到时才会新建。获取到的SqlSession是所有线程共享的,还是一个线程独占的呢?关于TransactionSynchronizationManager.getResource(...),本文并不打算分析,我们直接看它的方法说明:

从这个方法的说明来看,它就是用来返回与当前线程的绑定的资源的,这也就是说,通过TransactionSynchronizationManager.getResource(...)得到的SqlSession就是线程独占的,因此不存在线程安全问题。

另外,据本人验证,只有在开启事务时,SqlSession才会被保存,如果未开启事务,TransactionSynchronizationManager.getResource(...)得到的SqlSession一直为null,每次调用getSqlSession(...)时都会创建一个新的SqlSession.

到了这里,我们就明白了,在执行MapperInterface的方法时,获取到的SqlSession由spring容器托管,并且线程独占。

5. 总结

この記事では、主にmybatisとspringの統合プロセスを分析します。

  1. SqlSessionFactoryのプロパティ構成はこのクラスによって処理されます。このクラスは、一連の構成をカスタマイズするSqlSessionFactoryBeanための多くのsetXxx(...)メソッドを提供します。mybatis
  2. @MapperScan指定可能なスキャンパスMapperInterface。これらのパッケージパスはMapperScannerConfigurerクラスでスキャンされ、スプリングコンテナに登録されます。
  3. パケットパスをスキャンして取得したイニシャルbeanDefinitionbeanClassインターフェイスであり、これmybatis変換さClassPathMapperScanner#processBeanDefinitionsれます。これは、実際のタイプは;です。beanDefinitionbeanClassMapperFactoryBeanFactoryBeanMapperInterface
  4. 注入されるBeanが取得されると、対応するBeanがメソッドMapperInterfaceを介して取得され、取得されたBeanは動的プロキシオブジェクトであり、その中に;が含まれます。MapperFactoryBean#getObject(...)MapperInterfacesqlSessionSqlSessionTemplate
  5. SqlSessionTemplateメンバー変数があります。sqlSessionProxyそのタイプはSqlSession、メソッドが実行されると、呼び出されたデータベース操作メソッドMapperInterfaceが最終的に使用されます。sqlSessionProxySqlSession
  6. これsqlSessionProxyは動的プロキシオブジェクトであるため、メソッドが実行されると、メソッドを通過し、Interceptor#invoke(...)メソッドは現在のスレッドへのバインディングを取得しますDefaultSqlSession。これは実際の実行でも使用され、DefaultSqlSessionスレッドセーフの問題を回避します。

この記事の元のテキストへのリンク:my.oschina.net/funcy/blog/…、著者の個人的なレベルに限定されており、テキストには必然的に間違いがあります。私を訂正することを歓迎します!オリジナリティは簡単ではありません。商用の再版の場合は、著者に許可を求めてください。非商用の再版の場合は、出典を示してください。

おすすめ

転載: juejin.im/post/7102812278247915533