美文网首页
Druid多数据源配置&Datasurce动态切换

Druid多数据源配置&Datasurce动态切换

作者: 南岩飞雪 | 来源:发表于2019-09-29 22:22 被阅读0次

来源

  • 业务数据存在数据源A,统计数据存在数据源B(表不同)
  • 应用拆分,灰度切换数据源,部分数据使用数据源A,部分数据使用数据源C(表相同)

实现

场景1:

不同的表结构存在不同的库:数据源A和数据源B不同的表
使用MapperScan,不同的DAO绑定不同的数据源

数据源A 配置
@Configuration
@ConfigurationProperties(prefix = "spring.datasourceA")
@EnableTransactionManagement
@MapperScan(basePackages = {"com.xxx.xxx.xxx","com.xxx.xxx.yyy"})
public class DruidConfig implements TransactionManagementConfigurer {
...
...
数据源B 配置
@Configuration
@ConfigurationProperties(prefix = "spring.datasourceB")
@EnableTransactionManagement
@MapperScan(basePackages = {"com.xxx.xxx.zzz"})
public class DruidConfig implements TransactionManagementConfigurer {
...
...

场景2:

同样的表结构存在不同的库:数据源A 和 数据源C,表相同,根据启动环境或者业务判断选用不同的数据源
使用AbstractRoutingDataSourcedetermineCurrentLookupKey决定当前线程使用哪个数据源

But一开始的想法(被抛弃和走的弯路):

  1. 使用场景1的方式,配置两个不同的数据源,拷贝俩份DAO和mapper.xml,代码判断使用哪个数据源的DAO
  2. 使用场景1的方式,配置两个不同的数据源,拷贝俩份DAO和mapper.xml,通过注解和切面,切换DAO
  • 自定义注解AliasSource
@AliasSource("xxxcccDAO") // 另一个DAO的bean name
@Repository("xxxDAO")
public interface XxxDAO {
...
...
  • 自定义切面
@Aspect
@Component
public class AliasSourceAspect {
    @Pointcut("@within(com.xxx.annotation.AliasSource)")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
            // 通过判断环境或者线程变量 判断是否使用注解指定的bean,反射调用之
            Method method = ((MethodSignature) pjp.getSignature()).getMethod();
            Class<?> clazz = method.getDeclaringClass();
            AliasSource annotation = clazz.getAnnotation(AliasSource.class);
            String name = annotation.value();
            Object bean = SpringBeanUtil.getBean(name);
            Method realMethod = bean.getClass().getDeclaredMethod(method.getName(), method.getParameterTypes());
            return realMethod.invoke(bean, pjp.getArgs());
            
            // 不然直接反射调用原来的方法
            return pjp.proceed();
        } 
    }

  1. 使用了AbstractRoutingDataSourcedetermineCurrentLookupKey决定当前线程使用哪个数据源,但是切面切的不对,切DAO的时候为时过晚
@Aspect
@Component
public class DataSourceAspect {
    @Pointcut("(execution(public * com.xxx.xxx.xxx.dal..*(..)) 
                      || execution(public * com.xxx.xxx.yyy.dal..*(..))) 
                      && !execution(public * com.xxx.xxx.zzz.dal..*(..))")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
            // 通过判断环境或者业务类型,来指定线程变量
            if (环境A/C) {
                // 下面代码有解释这个环境变量,指定数据源用的,
                // 这里其实已经晚了,走到这个切面的时候,
                // 已经调用过`AbstractRoutingDataSource`的`determineCurrentLookupKey`先选好数据源了,得到connection了
                线程变量.set("dbA/dbC")`;
            }
            // 调用dao方法
            return pjp.proceed();
        } 
    }

原因1:在service层使用了事务(@TransactionalORtransactionTemplate.execute(new TransactionCallbackWithoutResult() {})),这中场景肯定会先获取connection,开启事务,然后才走到我们自己的切面,晚了
原因2:由于切面本身也是切的DAO,但是spirng mybatis自己的切面在前,先获取了connection,然后才走到我们自己的切面,晚了
SO~ 去掉切面,改成在webapp的filter(自定义DataSourceFilter)就先设置好环境变量(线程变量.set("dbA/dbC"))选择好当前线程使用的数据源;这样就能正确的拿到指定数据源的connection啦

数据源A 和 数据源C 配置
@Configuration
@ConfigurationProperties(prefix = "spring.datasource")// 注入两份配置
@EnableTransactionManagement
@MapperScan(basePackages = {"com.xxx.xxx.xxx","com.xxx.xxx.yyy"})
public class DruidConfig implements TransactionManagementConfigurer {
...
...
    @Bean
    @Primary
    @Override
    public DataSourceTransactionManager annotationDrivenTransactionManager() {
        DruidDataSource dataSourceA = createDruidDataSource(urlA, usernameA, passwordA);
        DruidDataSource dataSourceC = createDruidDataSource(urlC, usernameC, passwordC);
        AbstractRoutingDataSource dynamicDataSource = new AbstractRoutingDataSource() {
            @Override
            protected Object determineCurrentLookupKey() {
                // 1. 可以通过启动环境判断使用哪个数据源
                if (环境A/C) {
                    return "dbA/dbC";
                }
                // 2. 可以通过自己设置的线程变量的值来指定使用哪个数据源
                String db = 线程变量.get();
                return db;
            }
        };
        Map<Object, Object> dsMap = Maps.newHashMap();
        dsMap.put("dbA", dataSourceA);
        dsMap.put("dbC", dataSourceC);
        dynamicDataSource.setDefaultTargetDataSource(salaryDruidDataSource);
        dynamicDataSource.setTargetDataSources(dsMap);
        dynamicDataSource.afterPropertiesSet();
        return new DataSourceTransactionManager(dynamicDataSource);
    }

    private DruidDataSource createDruidDataSource(String url, String username, String password) {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setDriverClassName(driverClassName);
        ...
        ...
        return dataSource;
    }
...
...

参考

druid多数据源配置+Datasurce动态切换

AbstractRoutingDataSource动态数据源切换,AOP实现动态数据源切换

相关文章

网友评论

      本文标题:Druid多数据源配置&Datasurce动态切换

      本文链接:https://www.haomeiwen.com/subject/asxductx.html