Data Processer

发布于:2025-08-01 ⋅ 阅读:(18) ⋅ 点赞:(0)

DP项目创建之初作为中间层处理WMS系统的报表数据,正常WMS数据是SQL直接返回结果到WMS展示,如果需要一些特殊处理,比如多个数据库的数据整合,复杂数据计算等需求,则使用DP中转处理。
​ 后续添加了BPM对接CBS资金系统的功能,同样是作为中转,比如BPM流程到某个节点,调用DP的接口,由DP向CBS发起付款申请,并记录日志和管理申请结果,根据申请结果和付款结果再调用BPM的接口审批流程。中转的原因是BPM项目限制过多,且架构太老、部署复杂,开发不方便,所以放到DP来完成主要的业务操作。

一、自定义注解实现数据源切换

自定义注解



@Target(ElementType.METHOD)// 用于方法上
@Retention(RetentionPolicy.RUNTIME)//运行时保留
@Documented
public @interface DataSource {
    /**
     * 目标 datasource
     * @return datasource
     */
    DataSourceEnum value();
}

@Getter
public enum DataSourceEnum {
    WMS("wms",1),
    MES("mes",2),
    SAP("sap",7),
    BPM("bpm",8),
    UNKNOWN("unknown",-1),
    ;
    private final String key;
    private final Integer value;

    DataSourceEnum(String key, Integer value) {
        this.key = key;
        this.value = value;
    }

    public static DataSourceEnum getByValue(Integer value){
        for (DataSourceEnum mapping : DataSourceEnum.values()) {
            if (Objects.equals(mapping.getValue(), value)){
                return mapping;
            }
        }
        return UNKNOWN;
    }
}

切面类

ProceedingJoinPoint 类
ProceedingJoinPoint 是 AspectJ 框架提供的接口,它是环绕通知(@Around)中使用的特殊类型的连接点。其他通知使用JoinPoint即可。
作用:

  1. 代表被拦截的方法执行点
  2. 在环绕通知中,允许控制是否执行目标方法
  3. 提供对目标方法的完全控制,包括修改参数、处理返回值、捕获异常等
    主要方法:
  4. proceed() - 执行目标方法
  5. proceed(Object[] args) - 使用指定参数执行目标方法
  6. getSignature() - 获取方法签名
  7. getTarget() - 获取目标对象
  8. getArgs() - 获取方法参数

joinPoint.proceed() 方法调用
这行代码是环绕通知中最关键的部分。
作用:
实际执行被拦截的目标方法
如果不调用 proceed(),目标方法将不会被执行
返回目标方法的执行结果


@Slf4j
@Aspect
@Component
public class DataSourceAspect {
    @Around("@annotation(com.lzcer.dataprocessor.frame.annotation.DataSource)")//环绕通知,拦截所有添加了@DataSource注解的方法
    public Object changeDataSource(ProceedingJoinPoint joinPoint) throws Throwable {//ProceedingJoinPoint 用于获取对目标方法的完全控制
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();//获得目标方法签名
        Method method = signature.getMethod();//得到目标方法
        DataSource annotation = method.getAnnotation(DataSource.class);//获取实例注解,用于解析用到的数据源
        DataSourceEnum datasource = annotation.value();
        log.info("========change datasource : {}========", datasource.getKey());
        DynamicDataSourceContextHolder.setDataSource(datasource.getKey());
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        Object result = joinPoint.proceed();
        stopWatch.stop();
        log.info("method: {} time cost :{} ms",method.getName(),stopWatch.getTotalTimeMillis());
        return result;
    }
}

设置当前数据源,使用ThreadLocal存储当前线程使用的数据源

public class DynamicDataSourceContextHolder {
    // 使用ThreadLocal存储每个线程的数据源标识
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    // 设置当前线程的数据源标识
    public static void setDataSource(String dataSourceKey) {
        CONTEXT_HOLDER.set(dataSourceKey);
    }

    // 获取当前线程的数据源标识
    public static String getDataSource() {
        return CONTEXT_HOLDER.get();
    }

    // 清除当前线程的数据源标识
    public static void clearDataSource() {
        CONTEXT_HOLDER.remove();
    }
}

切换数据源的核心实现
Spring 提供的 AbstractRoutingDataSource 是实现动态数据源切换的核心类
它的核心机制是通过 determineCurrentLookupKey() 方法获取当前应该使用的数据源标识,然后在已配置的数据源映射中查找对应的实际数据源

public class DynamicDataSource extends AbstractRoutingDataSource {
    public DynamicDataSource(Object defaultDataSource, Map<Object, Object> targetDataSources){
        super.setDefaultTargetDataSource(defaultDataSource);
        super.setTargetDataSources(targetDataSources);
    }
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSource();
    }
}

数据源配置

@Data
@Component
@ConfigurationProperties("datasource")//通过@ConfigurationProperties注解将配置文件中的 datasource 部分映射为 Java 对象
public class DataSourceConfig {

    private Map<String, DbConfig> dbs;
    @Data
    public static class DbConfig {
        private String url;
        private String username;
        private String password;
        private String driverClassName;
    }
}

@AllArgsConstructor
@Configuration
public class DataSourceManager {
    private static final Map<Object, Object> DATA_SOURCE_MAP = new HashMap<>();
    private final DataSourceConfig config;

    @Bean(name = "dynamicDataSource")
    @Primary//优先选择
    public DynamicDataSource createDynamicDataSource() {
        // 遍历配置文件中的所有数据源配置
        config.getDbs().forEach((k, v) -> {
            if (DataSourceEnum.SAP.getKey().equals(k)) {
                // SAP 数据源使用 HikariDataSource
                HikariDataSource dataSource = new HikariDataSource();
                dataSource.setDriverClassName(v.getDriverClassName());
                dataSource.setJdbcUrl(v.getUrl());
                dataSource.setUsername(v.getUsername());
                dataSource.setPassword(v.getPassword());
                DATA_SOURCE_MAP.put(k, dataSource);
            } else {
                // 其他数据源使用 DruidDataSource
                DruidDataSource dataSource = new DruidDataSource();
                dataSource.setDriverClassName(v.getDriverClassName());
                dataSource.setUrl(v.getUrl());
                dataSource.setUsername(v.getUsername());
                dataSource.setPassword(v.getPassword());
                List<Filter> filters = new ArrayList<>();
                filters.add(statFilter());
                filters.add(wallFilter());
                dataSource.setProxyFilters(filters);
                try {
                    dataSource.setFilters("stat,wall,slf4j");
                } catch (SQLException e) {
                    e.printStackTrace();
                }
                DATA_SOURCE_MAP.put(k, dataSource);
            }
        });
        // 创建 DynamicDataSource,传入默认数据源和所有数据源映射
        return new DynamicDataSource(DATA_SOURCE_MAP.get("wms"), DATA_SOURCE_MAP);
    }
}

总结
实现数据源切换主要是用到了Spring框架提供的AbstractRoutingDataSource类,需要继承这个类,重写里面的determineCurrentLookupKey()方法。

  1. 需要在配置文件中配置号多个数据源的url、username、password、DriverClass等
  2. 新建DataSourceConfig类通过@ConfiguationProperties注解将配置文件中的配置变成JAVA中的对象
  3. 在 DataSourceManager 中创建实际的数据源对象
  4. DynamicDataSource初始化,DynamicDataSource 继承 AbstractRoutingDataSource,在初始化时设置默认数据源和目标数据源映射
  5. 运行时切换数据源,DataSourceAspect 切面通过 @Around(“@annotation(com.lzcer.dataprocessor.frame.annotation.DataSource)”) 拦截被 @DataSource 注解标记的方法
  6. DynamicDataSourceContextHolder.setDataSource() 将数据源键值存储到 ThreadLocal 中
  7. 执行目标方法触发数据源选择,当方法中有数据库操作时触发,调用我们实现的 determineCurrentLookupKey(),获取实际的数据库源,返回数据库源
  8. 执行数据库操作

网站公告

今日签到

点亮在社区的每一天
去签到