Spring中如何整合Mybatis

发布时间:2021-06-18 16:42:21 作者:Leah
来源:亿速云 阅读:160

Spring中如何整合Mybatis,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。

Spring整合Mybtais会进行如下的配置

private static final String ONE_MAPPER_BASE_PACKAGE = "com.XXX.dao.mapper.one";
@Bean
public MapperScannerConfigurer oneMapperScannerConfigurer() {
    MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
    mapperScannerConfigurer.setBasePackage(ONE_MAPPER_BASE_PACKAGE);
    mapperScannerConfigurer.
                setSqlSessionFactoryBeanName("oneSqlSessionFactoryBean");
    return mapperScannerConfigurer;
}
@Primary
@Bean(name="oneSqlSessionFactoryBean")
public SqlSessionFactoryBean oneSqlSessionFactoryBean( @Qualifier("oneDataSource") DruidDataSource oneDataSource) {
    return getSqlSessionFactoryBeanDruid(oneDataSource,ONE_MAPPER_XML);
}

短短不到20行代码,就完成了Spring整合Mybatis。

Amazing!!! 这背后到底发生了什么?

还要从MapperScannerConfigurer 和SqlSessionFactoryBean 着手。

MapperScannerConfigurer

类注释

类图找关键方法

Spring中如何整合Mybatis

从类图上看MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware接口。各个接口具体含义如下:

查询,MapperScannerConfigurer的afterPropertiesSet方法如下,无具体扩展信息。

@Override public void afterPropertiesSet() throws Exception {
notNull(this.basePackage, "Property 'basePackage' is required"); 
}

结合MapperScannerConfigurer的注释与类图分析,确定其核心方法为:postProcessBeanDefinitionRegistry

postProcessBeanDefinitionRegistry分析

@Override
public void postProcessBeanDefinitionRegistry(
                    BeanDefinitionRegistry registry) {
  if (this.processPropertyPlaceHolders) {
      //1. 占位符属性处理
    processPropertyPlaceHolders();
  }

  ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
  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);
  //2.设置过滤器
  scanner.registerFilters();
  //3.扫描java文件
  scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, 
          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

从源码中看到除了processPropertyPlaceHolders外,其他工作都委托了ClassPathMapperScanner

processPropertyPlaceHolders处理占位符

之前说BeanDefinitionRegistryPostProcessor在BeanFactoryPostProcessor执行前调用,

这就意味着Spring处理占位符的类PropertyResourceConfigurer还没有执行!

那MapperScannerConfigurer是如何支撑自己的属性使用占位符的呢?这一切的答案都在

processPropertyPlaceHolders这个方法中。

private void processPropertyPlaceHolders() {
  Map<String, PropertyResourceConfigurer> prcs =
       applicationContext.getBeansOfType(PropertyResourceConfigurer.class);
  if (!prcs.isEmpty() && applicationContext 
                      instanceof GenericApplicationContext) {
    BeanDefinition mapperScannerBean = 
            ((GenericApplicationContext) applicationContext)
                        .getBeanFactory().getBeanDefinition(beanName);
    // PropertyResourceConfigurer 没有暴露方法直接替换占位符,
    // 创建一个 BeanFactory包含MapperScannerConfigurer
    // 然后执行BeanFactory后处理即可
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    factory.registerBeanDefinition(beanName, mapperScannerBean);

    for (PropertyResourceConfigurer prc : prcs.values()) {
      prc.postProcessBeanFactory(factory);
    }
    PropertyValues values = mapperScannerBean.getPropertyValues();
    this.basePackage = updatePropertyValue("basePackage", values);
    this.sqlSessionFactoryBeanName =
            updatePropertyValue("sqlSessionFactoryBeanName", values);
    this.sqlSessionTemplateBeanName = 
            updatePropertyValue("sqlSessionTemplateBeanName", values);
  }
}

看完processPropertyPlaceHolders,可以总结 MapperScannerConfigurer支持它自己的属性使用占位符的方式

  1. 找到所有已经注册的PropertyResourceConfigurer类型的Bean

  2. 使用new DefaultListableBeanFactory()来模拟Spring环境,将MapperScannerConfigurer注册到这个BeanFactory中,执行BeanFactory的后处理,来替换占位符。

ClassPathMapperScanner的registerFilters方法

MapperScannerConfigurer的类注释中有一条:

通过annotationClass或markerInterface,可以设置指定扫描的接口,默认情况下这个2个属性为空,basePackage下的所有接口将被扫描。 scanner.registerFilters(),就是对annotationClass和markerInterface的设置。

public void registerFilters() {
  boolean acceptAllInterfaces = true;

  // 如果指定了annotationClass,
  if (this.annotationClass != null) {
    addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
    acceptAllInterfaces = false;
  }
  // 重写AssignableTypeFilter以忽略实际标记接口上的匹配项
  if (this.markerInterface != null) {
    addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
      @Override
      protected boolean matchClassName(String className) {
        return false;
      }
    });
    acceptAllInterfaces = false;
  }

  if (acceptAllInterfaces) {
    // 默认处理所有接口
    addIncludeFilter(new TypeFilter() {
      @Override
      public boolean match(
      MetadataReader metadataReader, 
      MetadataReaderFactory metadataReaderFactory) throws IOException {
        return true;
      }
    });
  }

  // 不包含以package-info结尾的java文件
  // package-info.java包级文档和包级别注释
  addExcludeFilter(new TypeFilter() {
    @Override
    public boolean match(MetadataReader metadataReader, 
    MetadataReaderFactory metadataReaderFactory) throws IOException {
      String className = metadataReader.getClassMetadata().getClassName();
      return className.endsWith("package-info");
    }
  });
}

虽然设置了过滤器,如何在扫描中起作用就要看scanner.scan方法了。

ClassPathMapperScanner的scan方法

public int scan(String... basePackages) {
   int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
   doScan(basePackages);
   // 注册注解配置处理器
   if (this.includeAnnotationConfig) {
      AnnotationConfigUtils
                  .registerAnnotationConfigProcessors(this.registry);
   }
   return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}

doScan方法如下:

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

位于ClassPathMapperScanner的父类ClassPathBeanDefinitionScanner的doScan方法,就是

扫描包下的所有java文件转换为BeanDefinition(实际是ScannedGenericBeanDefinition)。

processBeanDefinitions就是将之前的BeanDefinition转换为MapperFactoryBean的BeanDefinition。

至于过滤器如何生效(即annotationClass或markerInterface)呢?我一路追踪源码

Spring中如何整合Mybatis

终于在ClassPathScanningCandidateComponentProvider的isCandidateComponent找到了对过滤器的处理

protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
   for (TypeFilter tf : this.excludeFilters) {
      if (tf.match(metadataReader, this.metadataReaderFactory)) {
         return false;
      }
   }
   for (TypeFilter tf : this.includeFilters) {
      if (tf.match(metadataReader, this.metadataReaderFactory)) {
         return isConditionMatch(metadataReader);
      }
   }
   return false;
}

总结MapperScannerConfigurer的作用

MapperScannerConfigurer实现了beanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法

从指定的 basePackage的目录递归搜索接口,将它们注册为MapperFactoryBean

SqlSessionFactoryBean

类注释

  1. 创建Mybatis的SqiSessionFactory,用于Spring上下文中进行共享。

  2. SqiSessionFactory可以通过依赖注入到与mybatis的daos中。

  3. datasourcetransactionmanager,jtatransactionmanager与sqlsessionfactory想结合实现事务。

类图找关键方法

Spring中如何整合Mybatis

SqlSessionFactoryBean实现了ApplicationListener ,InitializingBean,FactoryBean接口,各个接口的说明如下:

应该重点关注afterPropertiesSet和getObject的方法。

关键方法分析

afterPropertiesSet方法

public void afterPropertiesSet() throws Exception {
  notNull(dataSource, "Property 'dataSource' is required");
  notNull(sqlSessionFactoryBuilder, 
              "Property 'sqlSessionFactoryBuilder' is required");
  state((configuration == null && configLocation == null) 
          || !(configuration != null && configLocation != null),
  "Property 'configuration' and 'configLocation' can not specified with together");
  this.sqlSessionFactory = buildSqlSessionFactory();
}

buildSqlSessionFactory看方法名称就知道在这里进行了SqlSessionFactory的创建,具体源码不在赘述。

getObject方法

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

总结SqlSessionFactoryBean

实现了InitializingBean的afterPropertiesSet,在其中创建了Mybatis的SqlSessionFactory

实现了FactoryBean的getObject 返回创建好的sqlSessionFactory。

疑问

看完这SqlSessionFactoryBean和MapperScannerConfigurer之后,不知道你是否有疑问!一般在Spring中使用Mybatis的方式如下:

ApplicationContext context=new AnnotationConfigApplicationContext();
UsrMapper  usrMapper=context.getBean("usrMapper");
实际上调用的是
sqlSession.getMapper(UsrMapper.class);

SqlSessionFactoryBean创建了Mybatis的SqlSessionFactory。MapperScannerConfigurer将接口转换为了MapperFactoryBean。那又哪里调用的sqlSession.getMapper(UsrMapper.class)呢???

MapperFactoryBean是这一切的答案(MapperFactoryBean:注意看我的名字---Mapper的工厂!!)

MapperFactoryBean说明

类注释

能够注入MyBatis映射接口的BeanFactory。它可以设置SqlSessionFactory或预配置的SqlSessionTemplate。
注意这个工厂仅仅注入接口不注入实现类

类图找关键方法

Spring中如何整合Mybatis

看类图,又看到了InitializingBean和FactoryBean!!!

再次重点关注afterPropertiesSet和getObject的实现!

关键方法分析

DaoSupport类中有afterPropertiesSet的实现如下:

public final void afterPropertiesSet()
     throws IllegalArgumentException, BeanInitializationException {
    this.checkDaoConfig();
    try {
        this.initDao();
    } catch (Exception var2) {
        throw
         new BeanInitializationException(
                             "Initialization of DAO failed",  var2);
    }
}

initDao是个空实现,checkDaoConfig在MapperFactoryBean中有实现如下:

protected void checkDaoConfig() {
  super.checkDaoConfig();

  notNull(this.mapperInterface, "Property 'mapperInterface' is required");

  Configuration configuration = getSqlSession().getConfiguration();
  if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
    try {
      configuration.addMapper(this.mapperInterface);
    } catch (Exception e) {
      logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
      throw new IllegalArgumentException(e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
}

关键的语句是configuration.addMapper(this.mapperInterface),将接口添加到Mybatis的配置中。

getObject方法超级简单,就是调用了sqlSession.getMapper(UsrMapper.class);

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

总结MapperFactoryBean

实现了InitializingBean的afterPropertiesSet方法,在其中将mapper接口设置到mybatis的配置中。

实现了FactoryBean的getObject 方法,调用了sqlSession.getMapper,返回mapper对象。

总结

Spring整合Mybatis核心3类:

MapperScannerConfigurer

实现了beanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法,在其中从指定的 basePackage的目录递归搜索接口,将它们注册为MapperFactoryBean类型的BeanDefinition

SqlSessionFactoryBean

实现了InitializingBean的afterPropertiesSet,在其中创建了Mybatis的SqlSessionFactory。

实现了FactoryBean的getObject 返回创建好的sqlSessionFactory。

MapperFactoryBean

实现了InitializingBean的afterPropertiesSet方法,将mapper接口设置到mybatis的配置中。

实现了FactoryBean的getObject 方法,调用了sqlSession.getMapper,返回mapper对象。

关于Spring中如何整合Mybatis问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注亿速云行业资讯频道了解更多相关知识。

推荐阅读:
  1. Spring整合Mybatis
  2. Spring Boot 整合 MyBatis

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

spring mybatis

上一篇:springboot中JPA的应用方法

下一篇:python清洗文件中数据的方法

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》