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);
// spring事务中使用MethodClassKey作为key,来缓存Advisor AbstractFallbackTransactionAttributeSource#attributeCache
// private static final ConcurrentHashMap<MethodClassKey, 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
静态数据源
以后我工作中需要使用时,在来补充。哈哈