SpringBoot实现MapperScan添加动态配置(占位符)

目录
  • MapperScan添加动态配置(占位符)
    • MapperScannerRegistrard的核心代码如下
    • 从上面代码中可以看出
  • 关于@MapperScan配置
    • 问题
    • 解决方案

MapperScan添加动态配置(占位符)

在对Mybatis自动扫描配置中,使用注解配置时,@MapperScan中的配置,通常配置如下:

@MapperScan(basePackages = {"com.aa.**.mapper","com.bb.**.mapper"}, sqlSessionFactoryRef = "sqlSessionFactory")

不支持在yml或properties文件中动态配置。因为MapperScan注解功能的实现类MapperScannerRegistrar实现的是ImportBeanDefinitionRegistrar。在对@Configuration注解标记的类配置时,实现占位符功能的PropertyPlaceholderAutoConfiguration还没有开始加载。

MapperScannerRegistrard的核心代码如下

List<String> basePackages = new ArrayList();
        basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
        basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));
        basePackages.addAll((Collection)Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList()));
        scanner.registerFilters();
        scanner.doScan(StringUtils.toStringArray(basePackages));

从上面代码中可以看出

为了拓展支持占位符动态配置,只需把basePackages的加载改写即可。

1、参考MapperScannerRegistrard,实现 ImportBeanDefinitionRegistrar和ResourceLoaderAware两个接口

2、为了动态读取配置文件信息,需要引入Environment,所以实现EnvironmentAware接口

3、代码

MapperScannerRegistrar.java

import org.mybatis.spring.mapper.ClassPathMapperScanner;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils; 
import java.lang.annotation.Annotation;
import java.util.*;
import java.util.stream.Collectors;
 
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware,EnvironmentAware {
    private Environment environment;
    private ResourceLoader resourceLoader;
    private  static  final Logger logger = LoggerFactory.getLogger(MapperScannerRegistrar. class);
 
    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }
 
    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
 
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { 
        AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScanner.class.getName()));
        if (mapperScanAttrs != null) {
            this.registerBeanDefinitions(mapperScanAttrs, registry);
        }
    }
 
    void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {
        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
        Optional var10000 = Optional.ofNullable(this.resourceLoader);
        Objects.requireNonNull(scanner);
        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((BeanNameGenerator) 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<String>();
        basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
        for (String pkg : annoAttrs.getStringArray("basePackages")) {
            if (StringUtils.hasText(pkg)) {
                String value = parsePlaceHolder(pkg);
                if(StringUtils.hasText(value)){
                    List<String> valueList = Arrays.asList(value.split(","));
                    for(String base : valueList){
                        basePackages.add(base);
                    }
                }
            }
        }
        basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList()));
        scanner.registerFilters();
        scanner.doScan(StringUtils.toStringArray(basePackages));
    }
 
    private String parsePlaceHolder(String pro) {
        if (StringUtils.hasText(pro) && pro.contains(PropertySourcesPlaceholderConfigurer.DEFAULT_PLACEHOLDER_PREFIX)) {
            String value = environment.getProperty(pro.substring(2, pro.length() - 1));
 
            if (logger.isDebugEnabled()) {
                logger.debug("find placeholder value " + value + " for key " + pro);
            }
 
            if (null == value) {
                throw new IllegalArgumentException("property " + pro + " can not find!!!");
            }
            return value;
        }
        return pro;
    }
}

MapperScanner.java

import org.mybatis.spring.mapper.MapperFactoryBean;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.Import; 
import java.lang.annotation.*;
 
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MapperScannerRegistrar.class})
public @interface MapperScanner {
    String[] value() default {}; 
    String[] basePackages() default {}; 
    Class<?>[] basePackageClasses() default {}; 
    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class; 
    Class<? extends Annotation> annotationClass() default Annotation.class; 
    Class<?> markerInterface() default Class.class; 
    String sqlSessionTemplateRef() default ""; 
    String sqlSessionFactoryRef() default ""; 
    Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
}

4、使用MapperScanner,占位符扫描配置

@MapperScanner(basePackages = { "${mybatis.mapperScanner.basePackage}" }, sqlSessionFactoryRef = "sqlSessionFactory")
mybatis:
  mapperScanner:
    basePackage: com.aa.**.mapper,com.bb.**.mapper

关于@MapperScan配置

问题

org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): top.yangzefeng.usercenter.user.service.UserService.getById

解决方案

常用配置:

@MapperScan("top.yangzefeng.usercenter.**.mapper")
  • @MapperScan("com.demo.mapper"):扫描指定包中的接口
  • @MapperScan("com.demo.*.mapper"):一个代表任意字符串,但只代表一级包,比如可以扫到com.demo.aaa.mapper,不能扫到com.demo.aaa.bbb.mapper
  • @MapperScan("com.demo.**.mapper"):两个代表任意个包,比如可以扫到com.demo.aaa.mapper,也可以扫到com.demo.aaa.bbb.mapper

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Springboot 扫描mapper接口的2种操作

    方式一: 在所有mapper接口使用@Mapper注解 @Mapper (将包中的所有接口都标注为DAO层接口) public interface UserMapper { UserInfo getUserInfo(@Param("userId") String userId); } 方式二: 在springboot的启动类使用@MapperScan注解 (作用:将指定包中的所有接口都标注为DAO层接口,相当于在每一个接口上写@Mapper) @SpringBootApplicatio

  • 如何在Java SpringBoot项目中配置动态数据源你知道吗

    目录 首先需要引入第三方依赖 只需要在配置文件中按照如下配置 创建如下两个数据库 entity mapper.xml mapper层 Service层 下面是两个测试方法 下面可以来看一下测试结果: 在我们工作中涉及到一些场景需要我们配置多数据源的操作,之前来说我们配置数据源需要写繁琐的配置类来配置我们的数据源,哪个是默认数据源等等,而现在我们可以使用"苞米豆"为我们提供的提供的第三方工具,只需要简单配置就可以实现多数据源之间的灵活切换了! 首先需要引入第三方依赖 <depend

  • 解决Spring使用@MapperScan问题

    目录 问题场景 问题根源 问题追溯 问题解决 SpringBoot  @MapperScan的注意事项 问题场景 今天小编在MyBatis 整合Spring 的时候,使用到了@MapperScan,在启动期出现了一个错误: Invalid default: public abstract java.lang.Class org.mybatis.spring.annotation.MapperScan.factoryBean() 对于这个错误,小编也是倍感无奈,怎么会出现这个错误呢,看一下我的依赖

  • Spring Boot + Mybatis多数据源和动态数据源配置方法

    网上的文章基本上都是只有多数据源或只有动态数据源,而最近的项目需要同时使用两种方式,记录一下配置方法供大家参考. 应用场景 项目需要同时连接两个不同的数据库A, B,并且它们都为主从架构,一台写库,多台读库. 多数据源 首先要将spring boot自带的DataSourceAutoConfiguration禁掉,因为它会读取application.properties文件的spring.datasource.*属性并自动配置单数据源.在@SpringBootApplication注解中添加ex

  • SpringBoot实现MapperScan添加动态配置(占位符)

    目录 MapperScan添加动态配置(占位符) MapperScannerRegistrard的核心代码如下 从上面代码中可以看出 关于@MapperScan配置 问题 解决方案 MapperScan添加动态配置(占位符) 在对Mybatis自动扫描配置中,使用注解配置时,@MapperScan中的配置,通常配置如下: @MapperScan(basePackages = {"com.aa.**.mapper","com.bb.**.mapper"}, sqlSe

  • 详解SpringBoot配置文件启动时动态配置参数方法

    序言 当我们要同时启用多个项目而又要使用不同端口或者变换配置属性时,我们可以在配置文件中设置${变量名}的变量来获取启动时传入的参数,从而实现了动态配置参数,使启用项目更加灵活 例子 server: port: ${PORT:50101} #服务端口 spring: application: name: xc‐govern‐center #指定服务名 eureka: client: registerWithEureka: true #服务注册,是否将自己注册到Eureka服务中 fetchReg

  • springboot整合Quartz实现动态配置定时任务的方法

    前言 在我们日常的开发中,很多时候,定时任务都不是写死的,而是写到数据库中,从而实现定时任务的动态配置,下面就通过一个简单的示例,来实现这个功能. 一.新建一个springboot工程,并添加依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency

  • 使用springboot+druid双数据源动态配置操作

    目录 一.yml配置 二.动态切换数据源配置文件 1.数据源db1 2.数据源db2 三.多数据源的mapper包最好是分开 四.代码中调用 总结 进行动态切换,需要在类里面配置,顺便解决mybatis-plus自带代码无法使用问题,直接上代码: 一.yml配置 数据源可以都是oracle的也可以一个是oracle一个是mysql的. spring: datasource: druid: db-type: com.alibaba.druid.pool.DruidDataSource #多数据源1

  • springboot多环境进行动态配置的方法

    目录 一.如何配置多环境 二.生效多环境的多种方式 2.1.spring.config.name 2.2.spring.profiles.active 2.3.pom中<profiles>标签 三.总结 在平时的开发中,经常会有多个环境,如何管理多个环境中的配置呐?一个是我们本地的开发环境,可以称为dev,一个是测试环境,我们称为test,最后还要有生产环境,称为prod.每个环境的配置都是不一样的,如何做到快捷方便的使用各自环境的配置绝对是开发中需要着重考虑的. 一.如何配置多环境 在spr

  • Springboot使用docker-compose实现动态配置过程

    目录 使用docker-compose实现动态配置 例如 如图所示 总结 使用docker-compose实现动态配置 Springboot应用在打包的时候我们希望可以在项目启动的时候可以修改application.property或者yml中的配置:而不是在打包docker镜像的时候这个值确定后面不可以修改,这个时候可以通过yml提供的环境变量的功能来实现这个需求: 在yml文件中,通过${Envirment_variable}的方式可以获取系统环境变量中的值:于是可以通过把环境变量配置在do

  • JAVA字符串占位符使用方法实例

    目录 使用 replace 函数动态填充字符串 使用 String.format() 占位符替换 String.format 使用 1.占位符 2.对字符或字符串操作 3.对整数操作 4.对浮点数操作 5.对日期时间操作 补充:Java替换字符串中的占位符 总结 使用 replace 函数动态填充字符串 String str="Hello {0},我是 {1},今年{2}岁"; str = str.replace("{0}", "CSDN");

  • 使用springboot配置和占位符获取配置文件中的值

    目录 springboot配置和占位符获取配置文件值 @PropertySource&加载指定的配置文件 @ImportResource 导入指定的配置文件 springboot配置文件,占位符的使用 首先要给到注解 配置文件 运行最后一个测试类 测试如下 springboot配置和占位符获取配置文件值 @PropertySource& 加载指定的配置文件 package com.example.springbootdemo.pojo; import com.alibaba.fastjso

  • 基于SPRINGBOOT配置文件占位符过程解析

    这篇文章主要介绍了基于SPRINGBOOT配置文件占位符过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.配置文件占位符 1.application.properties server.port=8088 debug=false product.id=ID:${random.uuid} product.name=da mao mao product.weight=${random.int} product.fristLinePrice

  • Springboot自带定时任务实现动态配置Cron参数方式

    目录 Springboot自带定时任务实现动态配置Cron参数 SpringBoot定时任务的四种实现方式(主要) spring动态配置cron表达式,不需要停服 SchedulingConfigurer接口实现动态加载cron表达式 Springboot自带定时任务实现动态配置Cron参数 同学们,我今天分享一下SpringBoot动态配置Cron参数.场景是这样子的:后台管理界面对定时任务进行管理,可动态修改执行时间,然后保存入库,每次任务执行前从库里查询时间,以达到动态修改Cron参数的效

随机推荐