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
靜態資料源
以后我作業中需要使用時,在來補充,哈哈
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/267362.html
標籤:java
下一篇:JSON(初識一)
