美文网首页
Spring Boot 多数据源动态切换开启事务后,数据源切换失

Spring Boot 多数据源动态切换开启事务后,数据源切换失

作者: 心中翼 | 来源:发表于2019-02-19 09:30 被阅读0次

    问题

    在项目中遇到需要使用多数据源的情况,解决办法是,使用注解,切面拦截来注入不同的dataSource。实现代码在底部。

    思路

    基本思路:在dao的方法前加上@TargetDataSource(ORDER_DATA_SOURCE)注解来表明使用的哪个数据源。

    解决办法

    事务开启一般是在service中开启,事务开启后会导致数据源切换失败,数据源切换需要在事务开启前执行。

    解决:

    1、@EnableAspectJAutoProxy(exposeProxy = true)

    2、数据源切入点@Pointcut增加service切入点,其次service中需要开启事务的方法写两个,

    3、方法1加@TargetDataSource(ORDER_DATA_SOURCE),方法2加@Transactional,

    4、控制器调用方法1。方法1(使用代理方式调用,而不是直接调用)调用方法2。

    总结:控制器->方法1(切换数据源,使用代理方式调用方法2)->方法2(开启事务,执行多个dao操作)

    代理调用示例:

    public class TestService{
        @TargetDataSource(ORDER_DATA_SOURCE)
        public void method1() {
            ((TestService)AopContext.currentProxy()).method2();
        }
    
        @Transactional
        public void method2() {
         //dao操作
        }
    }
    
    image.gif

    动态切换数据源示例:

    /**
     * 目标数据源注解,注解在方法上指定数据源的名称
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface TargetDataSource {
        String value();//此处接收的是数据源的名称
    }
    
    image.gif
    /**
     * 动态数据源持有者,负责利用ThreadLocal存取数据源名称
     */
    public class DynamicDataSourceHolder {
        /**
         * 本地线程共享对象
         */
        private static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();
    
        public static void putDataSource(String name) {
            THREAD_LOCAL.set(name);
        }
    
        public static String getDataSource() {
            return THREAD_LOCAL.get();
        }
    
        public static void removeDataSource() {
            THREAD_LOCAL.remove();
        }
    }
    
    
    image.gif
    /**
     * 目标数据源注解,注解在方法上指定数据源的名称
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface TargetDataSource {
        String value();//此处接收的是数据源的名称
    }
    
    image.gif
    @Component
    @Data
    @ConfigurationProperties(prefix = "spring")
    public class DBProperties {
        private DataSource datasource;
        private DataSource orderDatasource ;
    }
    
    image.gif
    @Configuration
    @EnableScheduling
    @Slf4j
    public class DataSourceConfig {
    
        @Autowired
        private DBProperties properties;
    
        @Bean(name = "dataSource")
        public DataSource dataSource() {
            //按照目标数据源名称和目标数据源对象的映射存放在Map中
            Map<Object, Object> targetDataSources = new HashMap<>();
            targetDataSources.put("datasource", properties.getDatasource());
            targetDataSources.put("orderDatasource", properties.getOrderDatasource());
            //采用是想AbstractRoutingDataSource的对象包装多数据源
            DynamicDataSource dataSource = new DynamicDataSource();
            dataSource.setTargetDataSources(targetDataSources);
            //设置默认的数据源,当拿不到数据源时,使用此配置
            dataSource.setDefaultTargetDataSource(properties.getDatasource());
            return dataSource;
        }
    
        @Bean
        public PlatformTransactionManager txManager() {
            return new DataSourceTransactionManager(dataSource());
        }
    
        public static final String ORDER_DATA_SOURCE = "orderDatasource";
    
    }
    
    image.gif
    @Component
    @Aspect
    @Slf4j
    public class DataSourceAspect {
        private static final Logger LOG = LoggerFactory.getLogger(DataSourceAspect.class);
    
        //切换放在mapper接口的方法上,所以这里要配置AOP切面的切入点
        //增加api 控制器切入点,是因为动态数据源切换需要在事务开启前执行,故需要在service前切换
        @Pointcut("execution( * com.lifeccp.besra.repository..*.*(..)) || execution( * com.lifeccp.besra.service..*.*(..))")
        public void dataSourcePointCut() {
        }
    
        @Before("dataSourcePointCut()")
        public void before(JoinPoint joinPoint) {
            Object target = joinPoint.getTarget();
            String method = joinPoint.getSignature().getName();
            Class claz = target.getClass() ;
            Class<?>[] clazz = target.getClass().getInterfaces();
    
            Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameterTypes();
            try {
                Method m = null ;
                if(clazz.length <= 0){
                    m = claz.getMethod(method,parameterTypes) ;
                }else{
                    m = clazz[0].getMethod(method, parameterTypes);
                }
                //如果方法上存在切换数据源的注解,则根据注解内容进行数据源切换
                if (m != null && m.isAnnotationPresent(TargetDataSource.class)) {
                    TargetDataSource data = m.getAnnotation(TargetDataSource.class);
                    String dataSourceName = data.value();
                    DynamicDataSourceHolder.putDataSource(dataSourceName);
                    LOG.info("current thread " + Thread.currentThread().getName() + " add " + dataSourceName + " to ThreadLocal");
                } else {
                    LOG.info("switch datasource fail,use default");
                }
            } catch (Exception e) {
                LOG.error("current thread " + Thread.currentThread().getName() + " add data to ThreadLocal error", e);
            }
        }
    
        //执行完切面后,将线程共享中的数据源名称清空
        @After("dataSourcePointCut()")
        public void after(JoinPoint joinPoint){
            DynamicDataSourceHolder.removeDataSource();
        }
    }
    
    
    image.gif

    相关文章

      网友评论

          本文标题:Spring Boot 多数据源动态切换开启事务后,数据源切换失

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