记录springboot多数据源的配置

1.要实现多数据源切换,肯定不能让springboot自动配置数据源,所以启动时,不设置自动配置数据,在启动类上使用一下代码

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

2.Spring中AbstractRoutingDataSource类的方法determineTargetDataSource() 是决定当前线程所用数据源的方法,如下源码

protected DataSource determineTargetDataSource() {
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
        Object lookupKey = determineCurrentLookupKey();
        DataSource dataSource = this.resolvedDataSources.get(lookupKey);
        if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
            dataSource = this.resolvedDefaultDataSource;
        }
        if (dataSource == null) {
            throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
        }
        return dataSource;
    }
resolvedDataSources是一共可以有多少个数据源,resolvedDefaultDataSource是默认的数据源,lenientFallback默认为true,determineCurrentLookupKey()是个抽象方法,确定当前查找键,用于检查线程绑定的事务上下文。
因此写一个类继承AbstractRoutingDataSource,实现determineCurrentLookupKey()是必须的
public class DynamicDataSource extends AbstractRoutingDataSource {

    private static Map<String, String> dataBaseDataSourceMapping = new HashMap<>();

    private static List<String> dataBases = new ArrayList<>();

    /**
     * 数据源路由,此方法用于产生要选取的数据源逻辑名称
     */
    @Override
    protected Object determineCurrentLookupKey() {
        //从共享线程中获取数据源名称
        return DataSourceContextHolder.getDataSource();
    }

//从数据库获得<数据库:数据源>集合,然后根据数据库得到该使用的数据源key
public static String getDataSource(String dataBase) { if (dataBaseDataSourceMapping.isEmpty()) { DataBaseDataSourceConfigMapper mapper = SpringUtils.getBean(DataBaseDataSourceConfigMapper.class); List<DataBaseDataSourceConfig> configs = mapper.findAllConfig(); configs.forEach(config -> dataBaseDataSourceMapping.put(config.getDataBase(), config.getDataSource())); } return dataBaseDataSourceMapping.get(dataBase); } //所有的数据源key 的枚举 public enum DataSourceType { DEFAULT("oghma"), OGHMA("oghma"), DW("dw"), QUARTZ("quartz"), CLS("cls"), OLD("old"); private String name; DataSourceType(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
DataSourceContextHolder存放本地线程共享对象,ThreadLocal作用不赘述
 
public class DataSourceContextHolder {
    private final static ThreadLocal<String> local = new ThreadLocal<>();

    public static void setDataSource(String name) {
        local.set(name);
    }

        public static String getDataSource() {
        return local.get();
    }

    public static void removeDataSource() {
        local.remove();
    }
}

3.配置所有数据源的信息,这里只给出一个模式,并指定事务所依赖的数据源

application.properties
#分库 
spring.datasource.cls.url=XXXXX
spring.datasource.cls.username=XX
spring.datasource.cls.password=XX
spring.datasource.cls.driver-class-name=XXX

 
@Configuration
public class DataSourceConfig {
    @Value("${spring.datasource.cls.url}")
    private String clsUrl; 
    
    @Value("${spring.datasource.cls.username}")
    private String clsUserName;
    
    @Value("${spring.datasource.cls.password}")
    private String clsPassword;
    
    @Value("${spring.datasource.cls.driver-class-name}")
    private String clsDriver;
    
    @Bean(name = "clsDataSource")
    public DataSource dataSourceCls() {
        return DataSourceBuilder.create().type(HikariDataSource.class)
                        .driverClassName(clsDriver)
                        .url(clsUrl)
                        .username(clsUserName)
                        .password(clsPassword).build();
    }
@Bean(name
= "dynamicDataSource") @Primary public DataSource dataSource(){ DynamicDataSource dynamicDataSource = new DynamicDataSource(); DataSource oghma = dataSourceOghma(); DataSource dw = dataSourceDW(); DataSource quartz = dataSourceQuartz(); DataSource cls = dataSourceCls(); DataSource old = dataSourceOld(); //设置默认数据源 dynamicDataSource.setDefaultTargetDataSource(old); //配置多个数据源 Map<Object,Object> map = new HashMap<>(); map.put(DataSourceType.OGHMA.getName(), oghma); map.put(DataSourceType.DW.getName(), dw); map.put(DataSourceType.QUARTZ.getName(), quartz); map.put(DataSourceType.CLS.getName(), cls); map.put(DataSourceType.OLD.getName(), old); dynamicDataSource.setTargetDataSources(map); return dynamicDataSource; }
//事务管理器 @Bean
public PlatformTransactionManager txManager() { return new DataSourceTransactionManager(dataSource()); } }

配置数据源有一种简单写法,使用@ConfigurationProperties(这种直接写在类中方法上,我没有弄成功,类上方法可以)

    @Bean(name = "clsDataSource")// 读取application.properties中的配置参数映射成为一个对象,prefix表示参数的前缀
    @ConfigurationProperties(prefix = "spring.datasource.cls")
    public DataSource dataSourceCls() {
        return DataSourceBuilder.create().build();
    }

当然,可以使用@PropertySource(value= {"classpath:jdbc.properties"}),将配置信息从application.properties移至jdbc.properties文件。

4.写一个注解,一个切面(一个拦截指定方法,另一个拦截指定类的所有方法),切面拦截并设置当前方法改使用哪个数据源,没有注解,就使用默认数据源.为了防止事务注解和自定义注解同时使用出现错误(当切事务面先执行的时候,线程还没有确定数据源,会报错),这里使用@Order指定自定义切面先于事务切面执行。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
@Inherited
public @interface TargetDataSource {

    /**
     * 如果确定用哪个数据源,可手动指定数据源名称
     */
    DataSourceType value() default DataSourceType.OGHMA;
    
    /**
     * 如无法确定数据源,则指定数据库,系统自动查找数据源
     */
    String dataBase() default "";
}

@Aspect
@Component
@Order(1) 
public class DataSourceAspect {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    // 切入点:service类的方法上(这个包的子包及所有包的里面的以Service结尾的类的任意方法名任意参数的方法)
    //@Pointcut("execution(* com.topideal.supplychain..*Service..*(..))")
    @Pointcut("@annotation(com.topideal.supplychain.oghma.annotation.TargetDataSource)")
    public void dataSourcePointCut() {
    }

    @Before("dataSourcePointCut()")
    private void before(JoinPoint joinPoint) {
        try {
            Method m = getMethod(joinPoint);
            // 如果 m 上存在切换数据源的注解,则根据注解内容进行数据源切换;如果不存在,则使用默认数据源
            if (m != null && m.isAnnotationPresent(TargetDataSource.class)) {
                TargetDataSource data = m.getAnnotation(TargetDataSource.class);
                if (!StringUtils.isEmpty(data.dataBase())) { // 配置了数据库,根据数据库得到数据源
                    String dataBase = (String) resolver(joinPoint, data.dataBase());
                    String dataSource = DynamicDataSource.getDataSource(dataBase);
                    if (!StringUtils.isEmpty(dataSource)) {
                        DataSourceContextHolder.setDataSource(dataSource);
                    } else {
                        throw new RuntimeException("dataBase : " + dataBase + "  没有数据源!");
                    }
                } else { // 指定数据源
                    DataSourceContextHolder.setDataSource(data.value().getName());
                }
                //logger.info("》》》》》》》 current thread " + Thread.currentThread().getName() + " add 【 " + data.value().getName() + " 】 to ThreadLocal");
            }
        } catch (Exception e) {
            DataSourceContextHolder.setDataSource(DataSourceType.DEFAULT.getName());
            e.printStackTrace();
        }
    }

    // 执行完切面后,将线程共享中的数据源名称清空
    @After("dataSourcePointCut()")
    public void after(JoinPoint joinPoint) {
        DataSourceContextHolder.removeDataSource();
    }

    private Method getMethod(JoinPoint pjp) throws Exception {
        Signature sig = pjp.getSignature();
        MethodSignature msig = null;
        if (!(sig instanceof MethodSignature)) {
            throw new IllegalArgumentException("该注解只能用于方法");
        } else {
            msig = (MethodSignature) sig;
            Object target = pjp.getTarget();
            Method currentMethod = target.getClass().getDeclaredMethod(msig.getName(), msig.getParameterTypes());
            return currentMethod;
        }
    }

    public Object resolver(JoinPoint joinPoint, String str) {
        if (str == null) return null;
        Object value = null;
        if (str.matches("#\\{\\D*\\}")) {// 如果name匹配上了#{},则把内容当作变量
            String newStr = str.replaceAll("#\\{", "").replaceAll("\\}", "");
            if (newStr.contains(".")) { // 复杂类型
                try {
                    value = complexResolver(joinPoint, newStr);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                value = simpleResolver(joinPoint, newStr);
            }
        } else { //非变量
            value = str;
        }
        return value;
    }


    private Object complexResolver(JoinPoint joinPoint, String str) throws Exception {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();

        String[] names = methodSignature.getParameterNames();
        Object[] args = joinPoint.getArgs();
        String[] strs = str.split("\\.");

        for (int i = 0; i < names.length; i++) {
            if (strs[0].equals(names[i])) {
                Object obj = args[i];
                Method dmethod = obj.getClass().getDeclaredMethod(getMethodName(strs[1]), null);
                Object value = dmethod.invoke(args[i]);
                return getValue(value, 1, strs);
            }
        }

        return null;

    }

    private Object getValue(Object obj, int index, String[] strs) {
        try {
            if (obj != null && index < strs.length - 1) {
                Method method = obj.getClass().getDeclaredMethod(getMethodName(strs[index + 1]), null);
                obj = method.invoke(obj);
                getValue(obj, index + 1, strs);
            }
            return obj;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private String getMethodName(String name) {
        return "get" + name.replaceFirst(name.substring(0, 1), name.substring(0, 1).toUpperCase());
    }

    private Object simpleResolver(JoinPoint joinPoint, String str) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String[] names = methodSignature.getParameterNames();
        Object[] args = joinPoint.getArgs();

        for (int i = 0; i < names.length; i++) {
            if (str.equals(names[i])) {
                return args[i];
            }
        }
        return null;
    }

}

@Aspect
@Component
@Order(6)
public class DataSourceAspect2 {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Pointcut("@within(com.topideal.supplychain.oghma.annotation.TargetDataSource)")
    public void dataSourcePointCut() {
    }

    @Before("dataSourcePointCut()")
    private void before(JoinPoint joinPoint) {
        try {
            Class<?> aClass = joinPoint.getTarget().getClass();
            TargetDataSource data = aClass.getAnnotation(TargetDataSource.class);
            if (data != null) {
                if (StringUtils.isEmpty(data.dataBase())) {
                    DataSourceContextHolder.setDataSource(data.value().getName());
                } else {
                    // 配置了数据库,根据数据库得到数据源
                    String dataSource = DynamicDataSource.getDataSource(data.dataBase());
                    if (!StringUtils.isEmpty(dataSource)) {
                        DataSourceContextHolder.setDataSource(dataSource);
                    } else {
                        throw new RuntimeException("dataBase : " + data.dataBase() + "  没有数据源!");
                    }
                }
            }


        } catch (Exception e) {
            DataSourceContextHolder.setDataSource(DataSourceType.DEFAULT.getName());

        }
    }

    // 执行完切面后,将线程共享中的数据源名称清空
    @After("dataSourcePointCut()")
    public void after(JoinPoint joinPoint) {
        DataSourceContextHolder.removeDataSource();
    }


}
DataSourceAspect直接写方法上即可,DataSourceAspect2可注解在抽象类上,假设@TargetDataSource(DataSourceType.DW),继承该抽象类的类,都使用数据源DW。前一个灵活性更高,具体看自己喜欢。

猜你喜欢

转载自www.cnblogs.com/carl-cn/p/12172962.html