跳到主要内容
  1. 所有文章/
  2. 《Mybatis3源码深度解析》笔记/

Mybatis源码-与Spring整合

·📄 5543 字·🍵 12 分钟

Mybatis与Spring整合 #

  1. 我们使用HSQLDB嵌入式数据库。
  2. 配置SqlSessionFactory对象我们知道 SqlSession 是 MyBatis 提供的与数据库交互的接口,而 SqlSession 的创建依赖于SqlSessionFactory 对象,因此我们需要创建 SqlSessionFactory 对象,并通过 Spring 来管理SqlSessionFactory对象的生命周期。
  3. 配置SqISessionTemplate 对象在使用MyBatis时,我们可以通过SqlSessionFactory对象的openSession()方法获取一个SqlSession对象,然后调用 SqlSession 对象提供的方法就可以与数据库进行交互了。每次调用SqlSessionFactory 对象的 openSession()方法返回的是一个新的实例,MyBatis Spring 模块提供了SqlSessionTemplate用于完成数据库交互,在整合Spring容器中只存在一个SqISessionTemplate实例。
  4. 通过MapperScan注解扫描Mapper接口
@SpringBootApplication
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
public class Application {
    public static void main(String[] args){
        SpringApplication.run(Application.class, args);
    }
}

@Configuration
@MapperScan(basePackages = {"com.blog4java.example.mapper"},
        sqlSessionTemplateRef="sqlSessionTemplate")
public class DataSourceConfiguration {
    @Bean(name = "dataSource")
    @Primary
    public DataSource setDataSource() {
        // 创建数据源Bean,并执行数据库脚本
        return new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.HSQL)
                .addScript("create-table-c12.sql")
                .addScript("init-data-c12.sql")
                .build();
    }

    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory setSqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
        // 通过Mybatis Spring模块提供的SqlSessionFactoryBean
        // 创建Mybatis的SqlSessionFactory对象
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:com/blog4java/example/mapper/*.xml"));
        return bean.getObject();
    }

    @Bean(name = "sqlSessionTemplate")
    @Primary
    public SqlSessionTemplate setSqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        // 创建Mybatis Spring模块中的SqlSessionTemplate对象
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    @Bean(name = "transactionManager")
    @Primary
    public DataSourceTransactionManager setTransactionManager(@Qualifier("dataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean
    @Primary
    public TransactionTemplate transactionTemplate(@Qualifier("transactionManager")DataSourceTransactionManager transactionManager) {
        return new TransactionTemplate(transactionManager);
    }
}

Spring关键接口 #

BanDefinition #

BeanDefinition用于描述SpringBean的配置信息,Spring配置Bean的方式通常有3种:

  1. XML配置文件,例如:
    <?xml version="1.o" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/xMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
      <bean id="executor"class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
        <property name="corePoolSize" value="200"/>
        <property name="maxPoolSize" value="500"/>
        <property name="queueCapacity" value="1000"/>
      </bean>
    </beans>
    
  2. Java注解,例如@Service、@Component等注解。Java注解的本质是一种轻量级的配置信息.
  3. JavaConfig方式,Spring从3.0版本开始支持使用@Configuration注解,通过JavaConfig方式配置Bean,这种方式在SpringBoot项目中比较流行,例如:
    @Configuration
    public class DataSourceConfiguration {
        @Bean(name = "dataSource")
        @Primary
        public DataSource setDataSource() {
            // 创建数据源Bean,并执行数据库脚本
            return new EmbeddedDatabaseBuilder()
                    .setType(EmbeddedDatabaseType.HSQL)
                    .addScript("create-table-c12.sql")
                    .addScript("init-data-c12.sql")
                    .build();
        }
    }
    

BanDefinitionRegistry #

BeanDefinitionRegistry是BeanDefinition容器,所有的Bean配置解析后生成的BeanDefinition对象都会注册到BeanDefinitionRegistry对象中。Spring提供了扩展机制,允许用户在Spring框架启动时,动态地往BeanDefinitionRegistry容器中注册BeanDefinition对象。

BeanFactory #

BeanFactory是Spring的Bean工厂,负责Bean的创建及属性注入。它同时是一个Bean容器,Spring框架启动后,会根据BeanDefinition对象创建Bean实例,所有的单例Bean都会注册到BeanFactory容器中。

BeanFactoryPostProcessor #

BeanFactoryPostProcessor是Spring提供的扩展机制,用于在所有的Bean配置信息解析完成后修改Bean工厂信息。例如,向BeanDefinitionRegistry容器中增加额外的 BeanDefinition 对象,或者修改原有的BeanDefinition对象。BeanFactoryPostProcessor是一个接口,该接口中只有一个方法,具体如下:

@FunctionalInterface
public interface BeanFactoryPostProcessor {
    void postProcessBeanFactory(ConfigurableListableBeanFactory var1) throws BeansException;
}

当我们配置的 Bean 实现该接口时,Spring 解析 Bean 配置完成后,就会调用所有BeanFactoryPostProcessor实现类的postProcessBeanFactory()方法。

ImportBeanDefinitionRegistrar #

ImportBeanDefinitionRegistrar是一个接口,该接口的实现类作用于Spring解析Bean的配置阶段,当解析@Configuration 注解时,可以通过ImportBeanDefinitionRegistrar接口的实现类向BeanDefinitionRegistry容器中添加额外的 BeanDefinition对象。ImportBeanDefinitionRegistrar接口定义如下:

public interface ImportBeanDefinitionRegistrar {
    void registerBeanDefinitions(AnnotationMetadata var1, BeanDefinitionRegistry var2);
}

ImportBeanDefinitionRegistrar 接口实现类的registerBeanDefinitions()方法会在 Spring 解析@Configuration 注解时调用。ImportBeanDefinitionRegistrar 接口需要配合@Import 注解使用,importingClassMetadata参数为@Import所在注解的配置信息,registry参数为BeanDefinition容器。

BeanPostProcessor #

Bean的后置处理器,在Bean初始化方法(init-method属性指定的方法或afterPropertiesSet()方法)调用前后,会执行BeanPostProcessor中定义的拦截逻辑。BeanPostProcessor接口定义如下:

public interface BeanPostProcessor {
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

BeanPostProcessor 接口中定义了两个方法,postProcessBeforeInitialization()方法会在所有Bean初始化方法调用之前执行,postProcessAfterInitialization()方法会在所有Bean的初始化方法调用之后执行。BeanPostProcessor 通常用于处理 SpringBean对应的Java类中的注解信息或者创建 Bean的代理对象。

ClassPathBeanDefinitionScanner #

ClassPathBeanDefinitionScanner是BeanDefinition扫描器,能够对指定包下的Class进行扫描,将Class信息转换为BeanDefinition对象注册到BeanDefinitionRegistry容器中。ClassPathBeanDefinitionScanner支持自定义的过滤规则,例如我们可以只对使用某种注解的类进行扫描。Spring中的@Service、@Component等注解配置Bean都是通过ClassPathBeanDefinitionScanner 实现的。MyBatis Spring 模块中 Mapper 接口的扫描使用到了ClassPathBeanDefinitionScanner类。

FactoryBean #

FactoryBean是 Spring中的工厂Bean,通常用于处理Spring中配置较为复杂或者由动态代理生成的Bean实例。实现了该接口的Bean不能作为普通的Bean使用,而是作为单个对象的工厂。当我们通过Bean 名称获取FactoryBean 实例时,获取到的并不是FactoryBean 对象本身,而是FactoryBean对象的getObject()方法返回的实例。例如如下Bean配置:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>

SqlSessionFactoryBean是一个FactoryBean,通过名称 sqlSessionFactory从 Spring容器中获取Bean时,获取到的实际上是SqlSessionFactoryBean对象的getObject()方法返回的对象。

Spring启动过程 #

Mapper动态代理对象注册过程 #

从MapperScan 注解中开始追踪

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
  // 扫描包路径
  String[] value() default {};
  // 扫描包路径
  String[] basePackages() default {};
  // 扫描Mapper接口对应的class对象
  Class<?>[] basePackageClasses() default {};
  // ean名称生成策略
  Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
  // 扫描使用某种注解修饰的类或接口
  Class<? extends Annotation> annotationClass() default Annotation.class;
  // 扫描某种类型的子类型
  Class<?> markerInterface() default Class.class;
  // 指定使用哪个SqlSessionTemplate对象
  String sqlSessionTemplateRef() default "";
  // 指定使用哪个SqlSessionFactory对象
  String sqlSessionFactoryRef() default "";
  // 指定使用自定义的MapperFactoryBean 返回Mybatis代理对象作为 Spring的 Bean
  Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
}
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

  private ResourceLoader resourceLoader;

  /**
   * {@inheritDoc}
   */
  @Override
  public void setResourceLoader(ResourceLoader resourceLoader) {
    this.resourceLoader = resourceLoader;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
      // 调用registerBeanDefinitions()方法注册BeanDefinition对象
      registerBeanDefinitions(mapperScanAttrs, registry);
    }
  }

  void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {
    // ClassPathMapperScanner是Mybatis Spring模块自定义的BeanDefinition扫描器
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

    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<>();
    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();
    // 对包中的类进行扫描生成BeanDefinition对象
    scanner.doScan(StringUtils.toStringArray(basePackages));
  }

  /**
   * A {@link MapperScannerRegistrar} for {@link MapperScans}.
   * @since 2.0.0
   */
  static class RepeatingRegistrar extends MapperScannerRegistrar {
    /**
     * {@inheritDoc}
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
        BeanDefinitionRegistry registry) {
      AnnotationAttributes mapperScansAttrs = AnnotationAttributes
          .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScans.class.getName()));
      if (mapperScansAttrs != null) {
        Arrays.stream(mapperScansAttrs.getAnnotationArray("value"))
            .forEach(mapperScanAttrs -> registerBeanDefinitions(mapperScanAttrs, registry));
      }
    }
  }

}

MapperScannerRegistrar 类实现了ImportBeanDefinitionRegistrar 接口的registerBeanDefinitions()方法,该方法调用了重载的registerBeanDefinitions()进行处理。在重载方法中,首先创建了一个ClassPathMapperScanner 对象,然后获取MapperScan注解的属性信息,根据MapperScan的annotationClass 和markerInterface 属性对扫描的Class 进行过滤,最后调用ClassPathMapperScanner对象的doScan()方法进行扫描。ClassPathMapperScanner 是Spring中ClassPathBeanDefinitionScanner的子类,用于扫描特定包下的Mapper 接口,将Mapper 接口信息转换为对应的BeanDefinition对象。下面是ClassPathMapperScanner 类doScan()方法的实现:

@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
  // 调用父类的doScan()方法,將包中的Class转换为BeanDefinitionHolder对象
  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 {
    // 对BeanDefinitionHolder进行处理
    processBeanDefinitions(beanDefinitions);
  }

  return beanDefinitions;
}

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
  GenericBeanDefinition definition;
  for (BeanDefinitionHolder holder : beanDefinitions) {
    // 获取BeanDefinition对象
    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
    definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
    // 將BeanDefinition对象的beanClass属性设置为MapperFactoryBean
    definition.setBeanClass(this.mapperFactoryBean.getClass());
    // 修改BeanDefinition对象的propertyValues属性,將sqlSessionFactory注入到MapperFactoryBean中
    definition.getPropertyValues().add("addToConfig", this.addToConfig);

    boolean explicitFactoryUsed = false;
    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;
    }

    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() + "'.");
      definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    }
  }
}

如上面的代码所示,在ClassPathMapperScanner 类的doScan()方法中,首先调用父类的doScan()方法,将指定包下的Mapper接口信息转换为BeanDefinitionHolder对象,BeanDefinitionHolder 中持有一个BeanDefinition对象及Bean的名称和所有别名。所有的Mapper接口转换为BeanDefinitionHolder 对象后,接着调用processBeanDefinitions()方法,对所有BeanDefinitionHolder对象进行处理。在processBeanDefinitions()方法中,对所有BeanDefinitionHolder 对象进行遍历,获取BeanDefinitionHolder对象中持有的BeanDefinition对象。然后对BeanDefinition对象的信息进行修改,将BeanDefinition对象的beanClass属性设置为MapperFactoryBean,并向BeanDefinition对象中增加几个PropertyValue对象,对应MapperFactoryBean的addToConfig和 sqlSessionTemplate等属性。

将BeanDefinition 对象的beanClass属性设置为MapperFactoryBean这一步很重要,当 Spring将所有的Bean配置信息转换为BeanDefinition对象后,就会根据BeanDefinition对象来实例化Bean。由于BeanDefinition对象的beanClass属性被设置为MapperFactoryBean,因此 Spring在创建Bean时实例化的是MapperFactoryBean对象。Spring会根据BeanDefinition对象中的PropertyValues对象对MapperFactoryBean 对象进行属性填充,因此MapperFactoryBean 对象的addToConfig和sqlSessionTemplate属性会被自动注入。

MapperFactoryBean 实现了FactoryBean 接口。当我们根据Mapper类型从Spring容器中获取FactoryBean时,获取到的并不是FactoryBean本身,而是FactoryBean的getObject()方法返回的对象。我们可以关注一下MapperFactoryBean的getObject()方法的实现,代码如下:

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }
} 

这里我们找到了想要的答案,在MapperFactoryBean的getObject()方法中,调用SqISession对象的getMapper()方法返回一个Mapper动态代理对象。

整合Spring事务管理 #

暂时搁置。