一、SpringBoot实现多数据源切换的多种方法
在生产环境中,我们会遇到需要跨多个数据源进行操作数据库的情况,下面我将分享出两种实现多数据源切换的方法。
项目代码:https://files.javaxing.com/Java%08Demo/springboot_dynamic_datasource.zip
1. AbstractRoutingDataSource
这种方式的核心是使用Spring提供的AbstractRoutingDataSource抽象类,注入多个数据源。
并且通过AOP的方式自己完成了读写分离,当然 如果不需要读写分离的话,也可以通过AOP的形式完成数据源切换。
1)基础工程
引入依赖
引入 springBoot、mybatis-plus、druid、mysql、aop依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.5</version> <relativePath/> </parent>
<groupId>com.javaxing</groupId> <artifactId>springboot_dynamic_datasource</artifactId> <version>1.0-SNAPSHOT</version> <packaging>pom</packaging> <modules> <module>dynamic_01</module> </modules>
<properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties>
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.2</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.6.7</version> </dependency>
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.3</version> </dependency>
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.29</version> </dependency> </dependencies> </project>
|
这里我们不仅实现了切换数据源,还实现了 基础版的读写分离,所以需要用到AOP切片。
2)编辑application.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| spring: datasource: type: com.alibaba.druid.pool.DruidDataSource datasource1: url: jdbc:mysql://10.211.55.12:3306/dynamic-order?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=false username: root password: 123123 initial-size: 1 min-idle: 1 max-active: 20 test-on-borrow: true driver-class-name: com.mysql.cj.jdbc.Driver datasource2: url: jdbc:mysql://10.211.55.12:3306/dynamic-order-02?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=false username: root password: 123123 initial-size: 1 min-idle: 1 max-active: 20 test-on-borrow: true driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus: mapper-locations: classpath:/mapper/*Mapper.xml
|
1、需要手动指定多个数据源信息,并为每个数据源创建一个名称,如:datasource1、datasource2
2、因为使用了mybatis-plus,所以需要指定Mapper.xml 位置
3)controller、service基本代码
entity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| public class Order { private Integer id;
private String name;
private BigDecimal price;
private Integer userId;
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public BigDecimal getPrice() { return price; }
public void setPrice(BigDecimal price) { this.price = price; }
public Integer getUserId() { return userId; }
public void setUserId(Integer userId) { this.userId = userId; } }
|
controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @RestController @RequestMapping("/test") public class TestController {
@Autowired private OrderService orderService;
@GetMapping("/read") public List<Order> read(){ return orderService.findAll(); }
@GetMapping("/write") public void write(){ Order order = new Order(); order.setName(System.currentTimeMillis()+""); order.setPrice(new BigDecimal(100.00)); orderService.insertSelective(order); } }
|
service
1 2 3 4 5
| public interface OrderService{ int insertSelective(Order record); public List<Order> findAll(); }
|
service.impl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Service public class OrderServiceImpl implements OrderService{
@Resource private OrderMapper orderMapper;
@Override @WR("W") public int insertSelective(Order record) { return orderMapper.insertSelective(record); }
@Override @WR("R") public List<Order> findAll(){ return orderMapper.findAll(); } }
|
mapper
1 2 3 4 5 6
| @Mapper public interface OrderMapper { int insertSelective(Order record);
List<Order> findAll(); }
|
4)配置DataSourceConfig数据源
底层会自动拿到spring.datasource中的配置, 创建一个DruidDataSource,自动完成数据源封装。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| package com.javaxing.config;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
@Configuration public class DataSourceConfig {
@Bean @ConfigurationProperties(prefix = "spring.datasource.datasource1") public DataSource dataSource1() { return DruidDataSourceBuilder.create().build(); }
@Bean @ConfigurationProperties(prefix = "spring.datasource.datasource2") public DataSource dataSource2() { return DruidDataSourceBuilder.create().build(); }
@Bean public DataSourceTransactionManager transactionManager1(DynamicDataSource dataSource){ DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); dataSourceTransactionManager.setDataSource(dataSource); return dataSourceTransactionManager; }
@Bean public DataSourceTransactionManager transactionManager2(DynamicDataSource dataSource){ DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); dataSourceTransactionManager.setDataSource(dataSource); return dataSourceTransactionManager; } }
|
5)AbstractRoutingDataSource注入到bean
- 继承Spring 提供的** AbstractRoutingDataSource(带路由功能的数据源)**
- 他会将数据源封装到key-value的实体里面,我们可以通过key完成数据源切换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| package com.javaxing.config;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Primary; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import org.springframework.stereotype.Component;
import javax.sql.DataSource; import java.util.HashMap; import java.util.Map;
@Component @Primary public class DynamicDataSource extends AbstractRoutingDataSource { public static ThreadLocal<String> name=new ThreadLocal<>();
@Autowired DataSource dataSource1; @Autowired DataSource dataSource2;
@Override protected Object determineCurrentLookupKey() { return name.get();
}
@Override public void afterPropertiesSet() {
Map<Object, Object> targetDataSources=new HashMap<>(); targetDataSources.put("W",dataSource1); targetDataSources.put("R",dataSource2); super.setTargetDataSources(targetDataSources);
super.setDefaultTargetDataSource(dataSource1);
super.afterPropertiesSet(); } }
|
6)AOP切片
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @Component @Aspect public class DynamicDataSourceAspect implements Ordered {
@Before("within(com.javaxing.service.impl.*) && @annotation(wr)") public void before(JoinPoint point, WR wr){ String name = wr.value(); DynamicDataSource.name.set(name);
System.out.println(name); }
@Override public int getOrder() { return 0; }
}
|
AOP接口
1 2 3 4 5 6
| @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface WR { String value() default "W"; }
|
7)实验测试
只需要在 service方法上 注解@WR(“W”),就可以实现 读写分离。
核心本质会通过AOP切片 完成数据源切换。
dynamic-order 写入了一条数据,而dynamic-order-02(读库)不会写入数据。
上面如果多数据源实现了读写分离,如果不想实现读写分离的话,也可以通过注解来实现多数据源切换。
只需要在注解里面写上你想要切换的数据源名称:@WR(“Name”)。
2. 使用dynamic-datasource框架(推荐)
dynamic-datasource是MyBaits-plus作者设计的一个多数据源开源方案。使用这个框架需要引入对应的pom依赖。
使用该框架可以非常简单、快速的实现多数据源切换。
1)基础工程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.2</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.6.7</version> </dependency>
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.3</version> </dependency>
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.29</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>3.5.0</version> </dependency> </dependencies>
|
2)application.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| spring: datasource: type: com.alibaba.druid.pool.DruidDataSource dynamic: primary: master strict: false datasource: master: url: jdbc:mysql://10.211.55.12:3306/dynamic-order?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=false username: root password: 123123 initial-size: 1 min-idle: 1 max-active: 20 test-on-borrow: true driver-class-name: com.mysql.cj.jdbc.Driver slave_1: url: jdbc:mysql://10.211.55.12:3306/dynamic-order-02?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=false username: root password: 123123 initial-size: 1 min-idle: 1 max-active: 20 test-on-borrow: true driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus: mapper-locations: classpath:/mapper/*Mapper.xml
|
使用dynamic-datasource框架爱的话,数据源要严格按照他们的要求去写的,并且需要给每个数据源命名,如 master、slave_1。
3)controlle等基础代码
controlle
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @RestController @RequestMapping("/test") public class TestController {
@Autowired private OrderService orderService;
@GetMapping("/read") public List<Order> read(){ return orderService.findAll(); }
@GetMapping("/write") public void write(){ Order order = new Order(); order.setName(System.currentTimeMillis()+""); order.setPrice(new BigDecimal(100.00)); orderService.insertSelective(order); } }
|
service
- 在需要数据源切换的方法上完成 @DS(“slave_1”) 注解,就可以实现数据源切换。
- @DS后面的value值 是在application.yaml里面配置的数据源地址名称。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Service public class OrderServiceImpl implements OrderService{
@Resource private OrderMapper orderMapper;
@Override @DS("master") public int insertSelective(Order record) { return orderMapper.insertSelective(record); }
@Override @DS("slave_1") public List<Order> findAll(){ return orderMapper.findAll(); } }
|
4)实验测试
访问写入controller:http://localhost:8080/test/write ,然后再次查看数据库,会发现 master库多出了一条新的记录。
而slave_1库则依然空空如也。