软件编程
位置:首页>> 软件编程>> java编程>> 详解SpringBoot+Mybatis实现动态数据源切换

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

作者:马小诺QAQ  发布时间:2022-06-04 02:42:02 

标签:SpringBoot,Mybatis,动态,数据源,切换

业务背景

电商订单项目分正向和逆向两个部分:其中正向数据库记录了订单的基本信息,包括订单基本信息、订单商品信息、优惠卷信息、发票信息、账期信息、结算信息、订单备注信息、收货人信息等;逆向数据库主要包含了商品的退货信息和维修信息。数据量超过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";
}

来源:https://www.jianshu.com/p/af0801ac95aa

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com