前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >踩坑集锦之Mybaits Invalid bound statement异常

踩坑集锦之Mybaits Invalid bound statement异常

作者头像
大忽悠爱学习
发布2023-10-11 08:39:57
1.7K0
发布2023-10-11 08:39:57
举报
文章被收录于专栏:c++与qt学习
踩坑集锦之Mybaits Invalid bound statement

引言

Invalid bound statement 算是Mybaits中比较常见的一个异常了:

代码语言:javascript
复制
org.apache.ibatis.binding.bindingexception: 
invalid bound statement (not found): 
com.dhy.testMapper.query

这个异常不管具体什么原因导致,归根结底,就是mapper接口和对应的xml没有绑定成功。

mapper接口和xml没有绑定成功存在很多原因,但是大部分原因都比较容易发现,本节我想来分享一个不那么容易发现的原因:

  • 这个原因一句话总结就是:
    • 多数据源配置场景下,存在多个MapperScannerConfigurer,并且他们的扫描范围存在重叠,那么可能会导致绑定失败。
    • 具体会不会绑定失败,跟多个MapperScannerConfigurer之间的执行顺序有关。

多数据源场景下Mybaits如何进行配置

假设我们有两个数据源,一个是DataSourceA,一个是DataSourceB,那么我们可以在Mybaits-Spring环境下这样配置:

代码语言:javascript
复制
    @Bean
    public SqlSessionFactory sqlSessionFactoryOfDataSourceA(@Qualifier("datasourceA") DataSource dataSourceA) throws Exception {
        SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSourceA);
        sessionFactoryBean.setConfigLocation(configLocation);
        sessionFactoryBean.setTypeAliasesPackage("com.dhy.mapper");
        Resource[] resources = new PathMatchingResourcePatternResolver().getResources("classpath:dhy/mapper/*.xml");
        sessionFactoryBean.setMapperLocations(resources);
        return sessionFactoryBean.getObject();
    }

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurerOfDataSourceA() {
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactoryOfDataSourceA");
        mapperScannerConfigurer.setBasePackage("com.dhy.mapper");
        return mapperScannerConfigurer;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactoryOfDataSourceB(@Qualifier("datasourceB") DataSource dataSourceB) throws Exception {
        SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSourceB);
        sessionFactoryBean.setConfigLocation(configLocation);
        sessionFactoryBean.setTypeAliasesPackage("com.dhy.mapper.b");
        Resource[] resources = new PathMatchingResourcePatternResolver().getResources("classpath:dhy/mapper/*.xml");
        sessionFactoryBean.setMapperLocations(resources);
        return sessionFactoryBean.getObject();
    }

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurerOfDataSourceB() {
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactoryOfDataSourceB");
        mapperScannerConfigurer.setBasePackage("com.dhy.mapper.b");
        return mapperScannerConfigurer;
    }
  • SqlSessionFactory核心作用有以下两个
    • 创建SqlSession,并使用SqlSession执行SQL语句和管理事务的核心接口
    • 管理配置信息Configuration,配置关系包括数据连接信息,映射文件配置,对象映射关系等
  • MapperScannerConfigurer核心作用是扫描并注册Mapper接口

通过上面的配置信息,我们可以观测到一点,那就是MapperScannerA和MapperScannerB扫描范围存在重叠,也就是MapperScannerA会把MapperScannerB的mapper接口也扫描进来:

其实默认情况下,mybaits-spring自身还会向IOC中自动注入一个MapperScanner,该mapper扫描器不同于我们上面配置的两个扫描器,它默认会基于SpringBoot自身包扫描范围开始,递归扫描并获取所有的标注了@Mapper注解的接口:

我们上面提供的两个扫描器是递归获取指定包路径下所有的类,不管是否加了@Mapper注解

  • 默认的扫描器由MybatisAutoConfiguration自动配置类完成注入
代码语言:javascript
复制
/**
 Auto-Configuration for Mybatis. 
 Contributes a SqlSessionFactory and a SqlSessionTemplate. 
 If org.mybatis.spring.annotation.MapperScan is used, 
 or a configuration file is specified as a property, 
 those will be considered, 
 otherwise this auto-configuration will attempt to register mappers 
 based on the interface definitions in or under the root auto-configuration package.
 */
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnBean(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration {
  ... 
  //如果我们不提供SqlSessionFactory,默认会自动注入一个
  @Bean
  @ConditionalOnMissingBean
  public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    ...
  }
  //如果我们不提供SqlSessionTemplate ,默认会自动注入一个
  @Bean
  @ConditionalOnMissingBean
  public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
       ...
  }

  /**
   MapperScan ultimately ends up creating instances of MapperFactoryBean. 
   If MapperScan is used then this auto-configuration is not needed. 
   If it is _not_ used, however, then this will bring in a bean registrar and automatically register components based on the same component-scanning path as Spring Boot itself.
   */
  @org.springframework.context.annotation.Configuration
  @Import({ AutoConfiguredMapperScannerRegistrar.class })
  @ConditionalOnMissingBean(MapperFactoryBean.class)
  public static class MapperScannerRegistrarNotFoundConfiguration {

    @PostConstruct
    public void afterPropertiesSet() {
      logger.debug("No {} found.", MapperFactoryBean.class.getName());
    }
  }

}

MybatisAutoConfiguration会在我们没有配置sqlSessionFactory和sqlSessionTemplate时,自动帮助我们注入一个,并且其内部的静态内部类MapperScannerRegistrarNotFoundConfiguration,会在我们没有使用MapperScan注解手动指定包扫描路径的情况下,帮我们导入一个AutoConfiguredMapperScannerRegistrar类,负责基于SpringBoot自身包扫描范围进行扫描。

代码语言:javascript
复制
  /**
   This will just scan the same base package as Spring Boot does. 
   If you want more power, you can explicitly use org.mybatis.spring.annotation.MapperScan but this will get typed mappers working correctly, out-of-the-box, similar to using Spring Data JPA repositories.
   */
  public static class AutoConfiguredMapperScannerRegistrar
      implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {
    private BeanFactory beanFactory;
    private ResourceLoader resourceLoader;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
      logger.debug("Searching for mappers annotated with @Mapper");
      //mybaits提供的mapper扫描器 
      ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
      try {
        if (this.resourceLoader != null) {
          scanner.setResourceLoader(this.resourceLoader);
        }
        //获取SpringBoot当前项目的包扫描路径
        List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
        ...
        //设置根据注解进行过滤
        scanner.setAnnotationClass(Mapper.class);
        //注册包扫描过程用用户过滤预期结果集合的Filter
        //我们上面设置了注解后,这里就会被封装为AnnotationTypeFilter注册进扫描器中
        scanner.registerFilters();
        //包扫描
        scanner.doScan(StringUtils.toStringArray(packages));
      }...
    }
    ...
  }

包扫描过程

调用mybaits提供的ClassPathMapperScanner类的scan方法完成包扫描过程:

ClassPathMapperScanner继承了ClassPathBeanDefinitionScanner,包扫描和根据Filter在扫描过程中进行过滤的能力都是Spring已经提供好了的。

代码语言:javascript
复制
  /**
   * Calls the parent search that will search and register all the candidates. Then the registered objects are post
   * processed to set them as MapperFactoryBeans
   */
  @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    //调用ClassPathBeanDefinitionScanner的doScan方法,完成包扫描和Filter过滤
    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 {
      //mybaits负责进行bean定义处理
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }

对于AutoConfiguredMapperScannerRegistrar来说,上面过滤得到的结果集合就是指定包路径集合下,所有加了@Mapper注解的类。

Mybaits在借助Spring的包扫描器完成过滤后,下一步就是对符合条件的BeanDefintion进行进一步处理:

包扫描得到的都是mapper接口,但是最终mybaits需要为这些接口生成代理对象,并且用户最终注入得到的Bean对象类型应该也是代理对象。 所以mybaits实际注入容器中的mapper类型为一个FactoryBean,即MapperFactoryBean,他的getObject方法中负责为当前mapper接口创建一个代理对象然后返回。

代码语言:javascript
复制
  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    AbstractBeanDefinition definition;
    BeanDefinitionRegistry registry = getRegistry();
    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
      //为MapperFactoryBean添加构造器参数
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
      //添加属性注入关系映射
      definition.getPropertyValues().add("mapperInterface", Resources.classForName(beanClassName));
      //设置当前bean实际类型为mapperFactoryBeanClass
      definition.setBeanClass(this.mapperFactoryBeanClass);
      //添加属性注入关系映射
      definition.getPropertyValues().add("addToConfig", this.addToConfig);
      ...
      //我们是否设置了ClassPathMapperScanner的sqlSessionFactoryBeanName属性
      //设置了该属性表明,由当前扫描器扫描得到的mapper接口,最终都会交给这个sqlSessionFactory管理
      boolean explicitFactoryUsed = false;
      if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
        //如果设置了,那么添加属性映射
        definition.getPropertyValues().add("sqlSessionFactory",
            //RuntimeBeanReference在bean的属性注入阶段会被替换为实际的sqlSessionFactoryBean实例
            new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionFactory != null) {
        //或者我们直接指定了sqlSessionFactory对象实例,那么就可以将RuntimeBeanReference直接替换为实际的bean实例
        definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
        explicitFactoryUsed = true;
      }
      
      //如果指定了sqlSessionTemplate也可以,处理同上
      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;
      }
     
      //如果sqlSessionTemplate和sqlSessionFactory都没有指定过,那么设置支持自动注入
      if (!explicitFactoryUsed) {
        LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
      //设置当前bean是否需要进行懒加载
      definition.setLazyInit(lazyInitialization);
      ...
       //作用域设置
      if (ConfigurableBeanFactory.SCOPE_SINGLETON.equals(definition.getScope()) && defaultScope != null) {
        definition.setScope(defaultScope);
      }
      ...
    }
  }

processBeanDefinitions方法中主要是为当前MapperFactoryBean指定好了相关的依赖注入映射关系,相当于手动操作propertyValues指定。

除了手动编码指定,我们还可以通过以下方式指定依赖注入映射关系:

  • xml配置文件指定
  • @AutoWired等注解方式指定
  • @Bean方式手动提前设置好相关属性值,从而省去了依赖注入步骤

还有一点需要大家注意,就是如果我们没有为当前ClassPathMapperScanner指定sqlSessionFactory或者sqlSessionTemplate的beanName或者bean实例,那么扫描器会为当前bean开启按照类型的自动注入:

  • 什么叫自动注入?
    • 我们可以通过上面提到的几种方式手动指定好依赖注入映射关系,从而在populateBean阶段由Spring根据我们设置好的映射关系通过bean后置处理器和BeanWrapper,分别根据注解方式指定的映射关系和当前BeanDefintion中的propertyValues集合中指定的映射关系完成对应的依赖注入
    • 后置处理器采用的是反射方式注入,BeanWrapper采用setter方法完成依赖注入
    • 如果注解方式和propertyValues方式指定的映射关系存在重叠,那么propertyValues优先级更高
    • 像xml方式进行的依赖注入映射关系配置,相关依赖注入映射关系都会保存在对应的beanDefintion的propertyValues集合中

自动注入就是说,我们在没有通过上述方式手动指定依赖注入映射关系的情况下,spring会自动尝试为我们当前bean对象进行依赖注入:

  • 自动注入又分为两种方式: 按照类型注入和按照名称注入

上述过程的逻辑体现在AbstractAutowireCapableBeanFactory的populateBean方法中,也就是bean的属性注入阶段:

代码语言:javascript
复制
	protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
		...
		//获取beanDefintion中已有的PropertyValues集合---集合中可能存在依赖注入映射关系了
		//例如: 我们通过xml配置了当前bean,或者像上面那样手动操作PropertyValues集合
		PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
		//获取当前bean的自动注入模式--默认是不开启的
		int resolvedAutowireMode = mbd.getResolvedAutowireMode();
		//如果当前bean开启了自动注入模式,那么判断是按照名称注入,还是按照类型注入
		//例如: 如果开启了自动注入模式,并且按照类型自动注入,那么spring会尝试为当前bean每个属性都进行自动注入
		//如果某几个属性可以按照类型自动注入成功,那么就创建好对应的依赖注入映射关系---propertyValue
		//然后把propertyValue加入当前bean已有的PropertyValues集合
		if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
			MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
			// Add property values based on autowire by name if applicable.
			if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {
				autowireByName(beanName, mbd, bw, newPvs);
			}
			// Add property values based on autowire by type if applicable.
			if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
				autowireByType(beanName, mbd, bw, newPvs);
			}
			pvs = newPvs;
		}
        //后置处理器处理注解方式指定的依赖关系映射注入逻辑省略
		...
        //最终使用BeanWrapper配合spring的类型转换模块
        //根据PropertyValues集合中保存的依赖注入映射关系,完成基于setter方法的依赖注入
		if (pvs != null) {
			applyPropertyValues(beanName, mbd, bw, pvs);
		}
	}

问题一: 自动注入带来的同类型bean实例冲突

上面说过,在没有为ClassPathMapperScanner指定sqlSessionTemplate或sqlSessionFactory的情况下,ClassPathMapperScanner会对其扫描到的每个MapperFactoryBean开启按照类型的自动注入模式:

  • 由于此时容器中存在两个类型都为SqlSessionFactory的bean,虽然bean名称不同,但是由于是按照类型自动注入,所以会抛出按照类型自动注入时发现同类型bean实例大于一个的异常

当然,发生这个问题的前提是自动注入的ClassPathMapperScanner扫描到了标注有@Mapper注解的mapper接口。

解决这个问题有两个思路:

  • 不要在mapper接口上标注@Mapper注解
  • 禁止自动注入ClassPathMapperScanner,可以尝试使用@MapperScan注解指明包扫描路径,这样就可以关闭自动注入ClassPathMapperScanner

@MapperScan注解背后的实现可以参考补充说明2

当然,一般情况下发生这个问题,可以通过在其中一个Bean上标注@Primary注解,告诉Spring在出现多个候选bean的情况下,优先选择标注有@Primary注解的Bean。


问题二: 扫描器扫描路径重叠,导致优先级低的扫描器扫描不到对应包路径下的mapper接口

在多数据源配置场景下,我们可以同时配置多个ClassPathMapperScanner和SqlSessionFactory,负责扫描不同包路径下的mapper接口。

不同包路径下的mapper接口分别属于不同的数据源,所以交给不同的SqlSessionFactory管理:

如上图所示,扫描器A和扫描器B分别扫描不同的包路径,但是扫描器A扫描的路径覆盖了扫描器B的路径,并且由于扫描器A优先于扫描器B执行,所以会导致扫描器B扫描不到对应包下mapper接口,这是为什么呢?

这是因为ClassPathBeanDefinitionScanner的doScan方法中,会将每个扫描器扫描得到的bean都进行注册,如果当前扫描器扫描到的某个bean已经存在于容器中了,那么当前扫描器则会跳过不进行处理:

代码语言:javascript
复制
	/**
	 Check the given candidate's bean name, determining whether the corresponding bean definition needs to be registered or conflicts with an existing definition.
	 */
	protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {
		if (!this.registry.containsBeanDefinition(beanName)) {
			return true;
		}
		BeanDefinition existingDef = this.registry.getBeanDefinition(beanName);
		BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition();
		if (originatingDef != null) {
			existingDef = originatingDef;
		}
		//两个bean的定义是相同的,即相互兼容的
		if (isCompatible(beanDefinition, existingDef)) {
			return false;
		}
		//如果两个bean是同名的,但是bean定义不兼容,则会抛出异常,报告出现了一个beanName映射到两个不同bean上的错误
		throw new ConflictingBeanDefinitionException("Annotation-specified bean name '" + beanName +
				"' for bean class [" + beanDefinition.getBeanClassName() + "] conflicts with existing, " +
				"non-compatible bean definition of same name and class [" + existingDef.getBeanClassName() + "]");
	}
  • 由于扫描器A的扫描路径覆盖了扫描器B的扫描路径,并且扫描器A优先于B执行,所有等到扫描器B执行的时候,它扫描得到的候选bean集合为空
  • 此时,又由于原本应该交给SqlSessionFactoryB管理的mapper接口,被扫描器A拿去了,并且交给了SqlSessionFactoryA管理,所以等到mapper方法调用时,就会抛出Invalid bound statement的异常!

解决我们上面遇到的这个问题,有两个思路:

  • 让扫描器B先执行,也就是需要让扫描器B这个工厂后置处理器优先于A执行,可以考虑自定义一个CustomMapperScannerConfigurer,然后让其继承Order接口或者直接在其类上标注@Order注解
  • 让CustomMapperScannerConfigurer优先级高于A即可

在@Bean方法上标注@Order注解影响的是Bean的加载顺序,不要搞错!

思路2就是让扫描器A扫描的路径和扫描器B扫描的路径不产生重叠:

同时注意关闭默认自动注入的扫描器,或者不在mapper接口上标注@Mapper注解即可


补充说明1: MapperScannerConfigurer

MapperScannerConfigurer本质是一个工厂Bean后置处理器,用于在BeanFactory prepare初始化完成后,向容器中额外添加一些BeanDefinition。

其核心方法是postProcessBeanDefinitionRegistry:

代码语言:javascript
复制
  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }
    //根据我们向MapperScannerConfigurer设置的值,再设置到ClassPathMapperScanner 中
    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);
    scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
    if (StringUtils.hasText(lazyInitialization)) {
      scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
    }
    if (StringUtils.hasText(defaultScope)) {
      scanner.setDefaultScope(defaultScope);
    }
    //注册Filter,然后启动包扫描,包扫描路径就是我们在@Bean方法中设置的
    scanner.registerFilters();
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

补充说明2: MapperScan注解原理

MapperScan注解最大的作用就是向容器中导入了一个MapperScannerRegistrar,MapperScannerRegistrar继承了ImportBeanDefinitionRegistrar接口,负责向容器中注册额外的beanDefintion。

而MapperScannerRegistrar向容器中注册的bean类型不是别的,就是上面介绍的MapperScannerConfigurer,MapperScan注解中的属性和MapperScannerConfigurer配置类中的属性一一对应:

代码语言:javascript
复制
@Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
     //取出注解中所有属性
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    //注册MapperScannerConfigurer
    if (mapperScanAttrs != null) {
      registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
          generateBaseBeanName(importingClassMetadata, 0));
    }
  }

  void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
      BeanDefinitionRegistry registry, String beanName) {
    // 要注册的bean类型为MapperScannerConfigurer
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    
    builder.addPropertyValue("processPropertyPlaceHolders", true);
    //如果指定了包扫描的同时按照注解进行过滤那就进行设置,默认是会将指定包下所有接口都搜集起来
    Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
      builder.addPropertyValue("annotationClass", annotationClass);
    }
    
    Class<?> markerInterface = annoAttrs.getClass("markerInterface");
    if (!Class.class.equals(markerInterface)) {
      builder.addPropertyValue("markerInterface", markerInterface);
    }

    Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
      builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
    }

    Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
      builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
    }

    String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
    if (StringUtils.hasText(sqlSessionTemplateRef)) {
      builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
    }

    String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
    if (StringUtils.hasText(sqlSessionFactoryRef)) {
      builder.addPropertyValue("sqlSessionFactoryBeanName", 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()));
    //没有指定包扫描路径,则选取默认包扫描路径
    if (basePackages.isEmpty()) {
      basePackages.add(getDefaultBasePackage(annoMeta));
    }

    String lazyInitialization = annoAttrs.getString("lazyInitialization");
    if (StringUtils.hasText(lazyInitialization)) {
      builder.addPropertyValue("lazyInitialization", lazyInitialization);
    }

    String defaultScope = annoAttrs.getString("defaultScope");
    if (!AbstractBeanDefinition.SCOPE_DEFAULT.equals(defaultScope)) {
      builder.addPropertyValue("defaultScope", defaultScope);
    }

    builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));

    // for spring-native
    builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);

    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

  }
  • 指定注入容器中的MapperScannerConfigurer的beanName
  • 默认包路径为当前@MapperScan标注的类的包名
代码语言:javascript
复制
  private static String generateBaseBeanName(AnnotationMetadata importingClassMetadata, int index) {
    return importingClassMetadata.getClassName() + "#" + MapperScannerRegistrar.class.getSimpleName() + "#" + index;
  }

  private static String getDefaultBasePackage(AnnotationMetadata importingClassMetadata) {
    return ClassUtils.getPackageName(importingClassMetadata.getClassName());
  }
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-06-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 踩坑集锦之Mybaits Invalid bound statement
  • 引言
  • 多数据源场景下Mybaits如何进行配置
  • 包扫描过程
  • 问题一: 自动注入带来的同类型bean实例冲突
  • 问题二: 扫描器扫描路径重叠,导致优先级低的扫描器扫描不到对应包路径下的mapper接口
  • 补充说明1: MapperScannerConfigurer
  • 补充说明2: MapperScan注解原理
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档