電商訂單項目分正向和逆向兩個部分:其中正向數據庫記錄了訂單的基本信息,包括訂單基本信息、訂單商品信息、優惠卷信息、發票信息、帳期信息、結算信息、訂單備註信息、收貨人信息等;逆向數據庫主要包含了商品的退貨信息和維修信息。數據量超過500萬行就要考慮分庫分表和讀寫分離,那麼咱們在正向操做和逆向操做的時候,就須要動態的切換到相應的數據庫,進行相關的操做。mysql
如今項目的結構設計基本上是基於MVC的,那麼數據庫的操做集中在dao層完成,主要業務邏輯在service層處理,controller層處理請求。假設在執行dao層代碼以前可以將數據源(DataSource)換成咱們想要執行操做的數據源,那麼這個問題就解決了spring
@Data
public class Product {
private Integer id;
private String name;
private Double price;
}
複製代碼
public interface ProductMapper {
@Select("select * from product")
public List<Product> findAllProductM();
@Select("select * from product")
public List<Product> findAllProductS();
}
複製代碼
@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中配置兩個數據源sql
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;
}
}
複製代碼
而後,咱們用Spring內置的RoutingDataSource,把兩個真實的數據源代理爲一個動態數據源:markdown
public class RoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return RoutingDataSourceContext.getDataSourceRoutingKey();
}
}
複製代碼
用於存儲當前須要切換爲哪一個數據源app
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中代碼)ide
@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 = ...代碼,使用起來十分不便。如下是優化方案ui
咱們能夠申明一個自定義註解,將以上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();
}
}
複製代碼
@RoutingWith("masterDataSource")
@GetMapping("/findAllProductM")
public String findAllProductM() {
productService.findAllProductM(); return "lagou";
}
@RoutingWith("slaveDataSource")
@GetMapping("/findAllProductS")
public String findAllProductS() {
productService.findAllProductS(); return "lagou";
}
複製代碼
以上就是實現以及優化的全部代碼,給菜雞一個贊吧😭😭😭😭