美文网首页Java 程序员Java
SpringBoot + Mybatis实现动态数据源切换

SpringBoot + Mybatis实现动态数据源切换

作者: 马小莫QAQ | 来源:发表于2021-03-09 15:59 被阅读0次

    业务背景

    电商订单项目分正向和逆向两个部分:其中正向数据库记录了订单的基本信息,包括订单基本信息、订单商品信息、优惠卷信息、发票信息、账期信息、结算信息、订单备注信息、收货人信息等;逆向数据库主要包含了商品的退货信息和维修信息。数据量超过500万行就要考虑分库分表和读写分离,那么我们在正向操作和逆向操作的时候,就需要动态的切换到相应的数据库,进行相关的操作。

    解决思路

    现在项目的结构设计基本上是基于MVC的,那么数据库的操作集中在dao层完成,主要业务逻辑在service层处理,controller层处理请求。假设在执行dao层代码之前能够将数据源(DataSource)换成我们想要执行操作的数据源,那么这个问题就解决了

    环境准备:

    1.实体类

    @Data
    public class Product {    
        private Integer id;    
        private String name;    
        private Double price;
    }
    

    2.ProductMapper

    public interface ProductMapper { 
        @Select("select * from product") 
        public List<Product> findAllProductM(); 
        @Select("select * from product") 
        public List<Product> findAllProductS(); 
    } 
    

    3.ProductService

    @Service 
    public class ProductService { 
        @Autowired 
        private ProductMapper productMapper; 
        public void findAllProductM(){ 
            // 查询Master 
            List<Product> allProductM = productMapper.findAllProductM(); 
            System.out.println(allProductM); 
        }
        public void findAllProductS(){ 
            // 查询Slave 
            List<Product> allProductS = productMapper.findAllProductS(); 
            System.out.println(allProductS); 
        } 
    }
    

    具体实现

    第一步:配置多数据源

    首先,我们在application.properties中配置两个数据源

    spring.druid.datasource.master.password=root 
    spring.druid.datasource.master.username=root 
    spring.druid.datasource.master.jdbc- url=jdbc:mysql://localhost:3306/product_master? useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC 
    spring.druid.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver 
    
    spring.druid.datasource.slave.password=root 
    spring.druid.datasource.slave.username=root 
    spring.druid.datasource.slave.jdbc- url=jdbc:mysql://localhost:3306/product_slave? useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC 
    spring.druid.datasource.slave.driver-class-name=com.mysql.cj.jdbc.Driver
    在SpringBoot的配置代码中,我们初始化两个数据源:
    
    @Configuration 
    public class MyDataSourceConfiguratioin { 
        Logger logger = LoggerFactory.getLogger(MyDataSourceConfiguratioin.class); 
        /*** Master data source. */ 
        @Bean("masterDataSource") 
        @ConfigurationProperties(prefix = "spring.druid.datasource.master") 
        DataSource masterDataSource() { 
            logger.info("create master datasource..."); 
            return DataSourceBuilder.create().build(); 
        }
    
        /*** Slave data source. */ 
        @Bean("slaveDataSource") 
        @ConfigurationProperties(prefix = "spring.druid.datasource.slave") 
        DataSource slaveDataSource() { 
            logger.info("create slave datasource..."); 
            return DataSourceBuilder.create().build(); 
        } 
    
        @Bean
        @Primary
        DataSource primaryDataSource(@Autowired @Qualifier("masterDataSource")DataSource masterDataSource,
    
                                     @Autowired @Qualifier("masterDataSource")DataSource slaveDataSource){
    
            logger.info("create routing datasource..."); 
            Map<Object, Object> map = new HashMap<>(); 
            map.put("masterDataSource", masterDataSource); 
            map.put("slaveDataSource", slaveDataSource); 
            RoutingDataSource routing = new RoutingDataSource(); 
            routing.setTargetDataSources(map); 
            routing.setDefaultTargetDataSource(masterDataSource); 
            return routing; 
    
        }
    
    }
    

    第二步:编写RoutingDataSource

    然后,我们用Spring内置的RoutingDataSource,把两个真实的数据源代理为一个动态数据源:

    public class RoutingDataSource extends AbstractRoutingDataSource { 
        @Override 
        protected Object determineCurrentLookupKey() { 
            return RoutingDataSourceContext.getDataSourceRoutingKey();
        } 
    } 
    

    第三步:编写RoutingDataSourceContext

    用于存储当前需要切换为哪个数据源

    public class RoutingDataSourceContext { 
        // holds data source key in thread local: 
        static final ThreadLocal<String> threadLocalDataSourceKey = new ThreadLocal<>(); 
        public static String getDataSourceRoutingKey() { 
            String key = threadLocalDataSourceKey.get(); 
            return key == null ? "masterDataSource" : key; 
        }
        public RoutingDataSourceContext(String key) { 
            threadLocalDataSourceKey.set(key); 
        }
        public void close() { 
            threadLocalDataSourceKey.remove(); 
        }
    }
    

    测试(一下代码为controller中代码)

    @GetMapping("/findAllProductM")
    public String findAllProductM() {    
        String key = "masterDataSource";    
        RoutingDataSourceContext routingDataSourceContext = new RoutingDataSourceContext(key);    
        productService.findAllProductM();    
        return "master";
    }
    @GetMapping("/findAllProductS")
    public String findAllProductS() {    
        String key = "slaveDataSource";
        RoutingDataSourceContext routingDataSourceContext = new RoutingDataSourceContext(key);
        productService.findAllProductS();
        return "slave";
    }
    

    以上代码即可实现数据源动态切换

    优化:

    以上代码是可行的,但是,需要读数据库的地方,就需要加上一大段RoutingDataSourceContext

    ctx = ...代码,使用起来十分不便。以下是优化方案

    我们可以申明一个自定义注解,将以上RoutingDataSourceContext中的值,放在注解的value属性中,

    然后定义一个切面类,当我们在方法上标注自定义注解的时候,执行切面逻辑,获取到注解中的值,set到RoutingDataSourceContext中,从而实现通过注解的方式,来动态切换数据源

    以下是代码实现:

    注解类

    @Target(ElementType.METHOD) 
    @Retention(RetentionPolicy.RUNTIME) 
    public @interface RoutingWith {
     String value() default "master";
     }
    

    切面类:

    @Aspect 
    @Component 
    public class RoutingAspect {
     @Around("@annotation(routingWith)")
     public Object routingWithDataSource(ProceedingJoinPoint joinPoint, RoutingWith routingWith) throws Throwable {
         String key = routingWith.value();
         RoutingDataSourceContext ctx = new RoutingDataSourceContext(key);
         return joinPoint.proceed();
     }
    } 
    

    改造Controller方法

    @RoutingWith("masterDataSource") 
    @GetMapping("/findAllProductM") 
    public String findAllProductM() {
     productService.findAllProductM(); return "lagou"; 
    }
    
    @RoutingWith("slaveDataSource") 
    @GetMapping("/findAllProductS") 
    public String findAllProductS() {
      productService.findAllProductS(); return "lagou";
     }
    

    以上就是实现以及优化的所有代码,给菜鸡一个赞吧

    作者:yusiji
    链接:https://juejin.cn/post/6937481321522397221
    来源:掘金

    相关文章

      网友评论

        本文标题:SpringBoot + Mybatis实现动态数据源切换

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