SpringBoot基于AOP多数据源

SB整合MP多数据源

在实际工作过程中,可以会遇到需要配置动态数据源的问题,本小节提供SB整合MP的动态多数据源问题,支持service层接口、实现类、Mapper接口 添加数据源注解实现方案。本小节还在项目启动时添加了ApplicationReadyEvent事件让其可以在提前缓存对应关系

动态数据源切换

// 定义数据源对应枚举常量
public enum DataSourceType {
    
    
  MASTER,
  SLAVE
}

public class DynamicDataSourceContextHolder extends AbstractRoutingDataSource {
    
    
  private static final ThreadLocal<DataSourceType> contextHolder 
    = new ThreadLocal<>();

  @Override
  protected Object determineCurrentLookupKey() {
    
    
    return contextHolder.get();
  }

  public static void setDataSource(DataSourceType value) {
    
    
    contextHolder.set(value);
  }

  public static void clearDataSource() {
    
    
    contextHolder.remove();
  }
}
@Configuration
@MapperScan("com.example.mapper")
public class DataSourceConfig {
    
    
  @Bean
  @ConfigurationProperties("spring.datasource.one")
  public DataSource master() {
    
    
    DataSource dataSource = new HikariDataSource();
    return dataSource;
  }

  @Bean
  @ConfigurationProperties("spring.datasource.two")
  public DataSource slave() {
    
    
    DataSource dataSource = new HikariDataSource();
    return dataSource;
  }

  @Primary
  @Bean
  @DependsOn({
    
    "master", "slave"})
  public DataSource dataSource(DataSource master, DataSource slave) {
    
    
    DynamicDataSourceContextHolder datasource
      = new DynamicDataSourceContextHolder();
    Map<Object, Object> targetDataSources = new HashMap<>(2);
    targetDataSources.put(DataSourceType.MASTER, master);
    targetDataSources.put(DataSourceType.SLAVE, slave);
    datasource.setTargetDataSources(targetDataSources);
    datasource.setDefaultTargetDataSource(master);
    return datasource;
  }
}

动态数据源注解切换

// 定义数据源切换注解
@Target({
    
    ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DynamicDataSourceSwitch {
    
    
  DataSourceType value() default DataSourceType.MASTER;
}
// 测试类
@DynamicDataSourceSwitch(dataSourceId = DataSourceType.SLAVE)
public interface IDemo {
    
    
    @DynamicDataSourceSwitch(dataSourceId = DataSourceType.MASTER)
    String hello();
}

@Service
// @DynamicDataSourceSwitch(dataSourceId = DataSourceType.MASTER)
public class Demo implements IDemo {
    
    
    // @DynamicDataSourceSwitch(dataSourceId = DataSourceType.SLAVE)
    @Override
    public String hello() {
    
    
        return "hello world";
    }
}
// 工具类:获取指定包下的类
public final class AnnotationMetaDataBeanUtil {
    
    
  private static Logger logger = LoggerFactory.getLogger(AnnotationMetaDataBeanUtil.class);
  //扫描  scanPackages 下的文件匹配符
  public static final String DEFAULT_RESOURCE_PATTERN = "/**/*.class";

  // 获取Spring资源解析器
  private static final ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
  // 创建Spring中用来读取resource为class的工具类
  private static final MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);

  /**
  * 找到 scanPackages 下带注解 annotation 的全部类信息
  *
  * @param scanPackages 扫描包集合
  * @return fullClassNameSet 获取指定包及其子包下带有此注解的全类名集合
  */
  public static Set<String> findPackageAnnotationClass(String... scanPackages) {
    
    
    Set<String> annotationMetadataSet = new LinkedHashSet<>();

    if (scanPackages == null || scanPackages.length == 0) {
    
    
      return annotationMetadataSet;
    }
    for (String basePackage : scanPackages) {
    
    
      if (StringUtils.isBlank(basePackage)) {
    
    
        continue;
      }
      String packageSearchPath
        = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
        ClassUtils.convertClassNameToResourcePath(
        SystemPropertyUtils.resolvePlaceholders(basePackage))
        + DEFAULT_RESOURCE_PATTERN;

      // 获取packageSearchPath下的Resource,这里得到的Resource是Class信息
      try {
    
    
        Resource[] resources = 
          resourcePatternResolver.getResources(packageSearchPath);
        for (Resource resource : resources) {
    
    
          MetadataReader metadataReader = 
            metadataReaderFactory.getMetadataReader(resource);
          annotationMetadataSet.add(metadataReader.getAnnotationMetadata()
                                    .getClassName());
        }
      } catch (Exception e) {
    
    
        logger.error("获取包下面的类信息失败,package:" + basePackage, e);
      }
    }
    return annotationMetadataSet;
  }
}
@Order(1)
@Aspect
@Component
@Slf4j
public class DynamicDataSourceHandlerAspect {
    
    
    // 可以将这个换为 LRUCache   https://blog.csdn.net/weixin_43476471/article/details/114482973
    private static final ConcurrentHashMap<String, DataSourceType> cacheMap = new ConcurrentHashMap<>(256);

    // 这里不能只拦截注解,若注解标注在类上面呢
    @Pointcut("execution(* com.example.service..*(..)) || execution(* com.example.mapper..*(..))")
    public void pointCut() {
    
    
    }

	// 这里可以保证所有类都以被窗口加载完成
    @EventListener(ApplicationReadyEvent.class)
    public void init() {
    
    
        Set<String> annotationClass = AnnotationMetaDataBeanUtil.findPackageAnnotationClass("com.example.service.impl", "com.example.mapper");
        annotationClass.parallelStream().map(className -> ClassUtils.resolveClassName(className, null))
                .forEach(clazz -> ReflectionUtils.doWithLocalMethods(clazz, method -> {
    
    
                    DynamicDataSourceSwitch annotation = Optional.ofNullable(AnnotationUtils.findAnnotation(method, DynamicDataSourceSwitch.class))
                            .orElseGet(() -> AnnotationUtils.findAnnotation(clazz, DynamicDataSourceSwitch.class));
                    Optional.ofNullable(annotation).map(DynamicDataSourceSwitch::value)
                            .ifPresent(dataSourceType -> cacheMap.put(fullMethodname(clazz, method), dataSourceType));
                }));
        System.out.println(cacheMap);
    }

    public String fullMethodname(Class<?> clazz, Method method) {
    
    
        return clazz.getName() + "#" + method.getName();
    }

    @Before("pointCut()")
    public void doBefore(JoinPoint joinPoint) {
    
    
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        Class<?> clazz = joinPoint.getTarget().getClass().getName().startsWith("com.sun.proxy") ? method.getDeclaringClass() : joinPoint.getTarget().getClass();
        // 当获取不到时,向Map中加入DefaultTargetDataSource
        DataSourceType dataSourceType = cacheMap.computeIfAbsent(fullMethodname(clazz, method),
                methodName -> Optional.ofNullable(
                        Optional.ofNullable(AnnotationUtils.findAnnotation(method, DynamicDataSourceSwitch.class))
                                .orElseGet(() -> AnnotationUtils.findAnnotation(clazz, DynamicDataSourceSwitch.class))
                ).map(DynamicDataSourceSwitch::value).orElse(DataSourceType.MASTER));
        Optional.ofNullable(dataSourceType).ifPresent(DynamicDataSourceContextHolder::setDataSource);
    }

    // 清理掉当前设置的数据源,让默认的数据源不受影响
    @After("pointCut()")
    public void after() {
    
    
        DynamicDataSourceContextHolder.clearDataSource();
    }
}

具体配置

spring:
  datasource:
    one:
      type: com.zaxxer.hikari.HikariDataSource
      jdbc-url: jdbc:mysql:///busi?useSSL=false&characterEncoding=utf8
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password: root
    two:
      type: com.zaxxer.hikari.HikariDataSource
      jdbc-url: jdbc:mysql:///girls?useSSL=false&characterEncoding=utf8
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password: root

静态数据源

以后我工作中需要使用时,在来补充。哈哈

猜你喜欢

转载自blog.csdn.net/weixin_43476471/article/details/114455488