Mybatis-Spring整合原理

452 阅读3分钟

1、基于java Config配置

@Configuration
@MapperScan(basePackages = "org.wfs.mybatis.**.mapper",sqlSessionTemplateRef = "sqlSessionTemplate")
public class MybatisConfig {
    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSources(dataSource);
        PathMatchingResourcePatternResolver patternResolver = new PathMatchingResourcePatternResolver();
        bean.setMapperLocations(patternResolver.getResource("classpath:org/wfs/mybatis/**/mapper/*.*.xml"));
        return bean;
    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

数据源DataSource则自己选择

这边Mapper的xml放在包下,需要配置pom文件将其打包到jar中:

<build>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.xml</include>
            </includes>
        </resource>
    </resources>
</build>

当然你也可以将Mapper文件放在resources资源文件下

2、@MapperScan实现原理

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
	 ...
}

可以看到上面使用了spring的@Import(MapperScannerRegistrar.class)注解来实现

ImportBeanDefinitionRegistrar是一个接口,该接口的实现作用于spring的解析bean的配置阶段,当解析@Configuration注解时,可以通过ImportBeanDefinitionRegistrar接口的实现类,向BeanDefinitionRegistry容器中添加额外的BeanDefinition,需要配合@Import使用

2.1、ImportBeanDefinitionRegistrar接口

可以根据@Configuration注解类上的注解数据来注册一些自定义的Bean;BeanDefinitionRegistryPostProcessor由于生命周期的限制最好不要在这里注册

之前的版本就是在这里注册MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor这个类,使用BeanDefinitionRegistryPostProcessorpost.ProcessBeanDefinitionRegistry来扫描注册@Mapper接口

public interface ImportBeanDefinitionRegistrar {
   void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}

2.2、registerBeanDefinitions方法的实现

首先是获取@MapperScanner注解数据,然后调用具体的注册方法

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  AnnotationAttributes mapperScanAttrs = AnnotationAttributes
      .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
  if (mapperScanAttrs != null) {
    registerBeanDefinitions(mapperScanAttrs, registry);
  }
}

注册流程其实很简单:

  1. 创建ClassPathMapperScanner ,用来扫描配置的包,将@Mapper注解的接口注册到spring容器中

  2. 处理注解数据,设置到ClassPathMapperScanner中

  3. ClassPathMapperScanner.doScan方法,扫描并注册@Mapper接口

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

2.3、如何扫描包并注册

ClassPathMapperScanner 继承ClassPathBeanDefinitionScanner

在上面看到scanner.registerFilters();这个主要是注册过滤,只扫描自己需要的类,这个我们不必研究,这里将扫描所有的类,排除package-info.java

2.3.1、具体扫描方法

重写父类的方法,其实扫描注册还是使用父类的方法,只是需要自定义给bean赋值

@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 {
    processBeanDefinitions(beanDefinitions);
  }

  return beanDefinitions;
}

接下来看看给Bean定义赋值方法:

  1. 遍历bean定义

  2. 给构造函数设置入参为接口的class

  3. 设置beanClass为MapperFactoryBean,使用MapperFactoryBean.getObject创建代理类

  4. addToConfig默认设置为true,这个配置会将接口同包下同名的Mapper文件加载到Mybatis中,可以无需配置Mapper文件的扫描地址

  5. 设置sqlSessionTemplate、sqlSessionFactory

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
  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
    definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
    definition.setBeanClass(this.mapperFactoryBeanClass);

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

3、MapperFactoryBean创建代理

通过实现FactoryBean.getObject方法创建并且生成bean注入到spring容器中。

创建mapper接口代理主要就是将SqlSession放入代理对象;

所以我们需要了解一下SqlSessionTemplate,因为spring放入的是SqlSessionTemplate单例;

SqlSession不是一个线程安全的对象,spring是如何做到单例的线程安全:

其内部持有一个SqlSession的代理属性

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去调用的;

下面看一下代理的实现类,是SqlSessionTemplate的私有内部类:

  1. 将获取线程安全的SqlSession,本质就是利用ThreadLocal来存放

  2. 反射调用SqlSession对象方法

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