Spring内置了一个AbstractRoutingDataSource,它可以把多个数据源配置成一个Map,然后,根据不同的key返回不同的数据源。因为AbstractRoutingDataSource也是一个DataSource接口,因此,应用程序可以先设置好key, 访问数据库的代码就可以从AbstractRoutingDataSource拿到对应的一个真实的数据源,从而访问指定的数据库。
我们整理一下这个类切换数据源的运作方式,这个类在连接数据库之前会执行determineCurrentLookupKey()方法,这个方法返回的数据将作为key去targetDataSources中查找相应的值,如果查找到相对应的DataSource,那么就使用此DataSource获取数据库连接它是一个abstract类,所以我们使用的话,推荐的方式是创建一个类来继承它并且实现它的determineCurrentLookupKey()方法,这个方法介绍上面也进行了说明,就是通过这个方法进行数据源的切换。
实现案例:
1.准备两个数据库
2.pom导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<!--引入适配器-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
<!-- 配置数据库驱动和mybatis dependency -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
2.准备数据源配置信息
spring.druid.datasource.master.password=root
spring.druid.datasource.master.username=root
spring.druid.datasource.master.jdbc-url=jdbc:mysql://localhost:3306/springboot_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/springboot_slave?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
spring.druid.datasource.slave.driver-class-name=com.mysql.cj.jdbc.Driver
3.取消数据源自动配置
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})//数据源自动配置类排除
public class SpringbootDataaccessApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootDataaccessApplication.class, args);
}
}
4.准备配置类
(1)RoutingDataSourceContext
public class RoutingDataSourceContext {
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();
}
}
(2)AbstractRoutingDataSource
public class RoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return RoutingDataSourceContext.getDataSourceRoutingKey();
}
}
(3)MyDataSourceConfiguratioin
@Configuration
public class MyDataSourceConfiguratioin {
Logger logger = LoggerFactory.getLogger(MyDataSourceConfiguratioin.class);
/**
* * Master data source.
*/
@Bean//被@Bean标注,该方法返回值存入容器中,没有@Bean指定,所用的key默认就是方法名,即@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();
}
//@Qualifier类型相同时,根据名称进行注入
@Bean
@Primary//三个方法返回类型相同,以此方法返回为主
public DataSource primaryDataSource(
@Autowired @Qualifier("masterDataSource") DataSource masterDataSourve,
@Autowired @Qualifier("slaveDataSource") DataSource slaveDataSource
){
RoutingDataSource routingDataSource = new RoutingDataSource();
HashMap<Object, Object> map = new HashMap<>();
map.put("master",masterDataSourve);
map.put("slave",slaveDataSource);
routingDataSource.setTargetDataSources(map);
return routingDataSource;
}
}
5.准备实体类
public class Product {
private int id;
private String name;
private int num;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
@Override
public String toString() {
return "Product{" +
"id=" + id +
", name='" + name + '\'' +
", num=" + num +
'}';
}
}
6.准备Mapper
@Mapper
public interface ProductDao {
@Select("SELECT * FROM product")
List<Product> getProductM();
@Select("SELECT * FROM product")
List<Product> getProductS();
}
7.准备实现类
@Service
public class ProductService {
Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
ProductDao productDao;
public List<Product> findAllProductM() {
List<Product> list = productDao.getProductM();
logger.info("查询出来的信息,{}", list.toString());
return list;
}
public List<Product> findAllProductS() {
List<Product> list = productDao.getProductS();
logger.info("查询出来的信息,{}", list.toString());
return list;
}
}
8.准备controller
@RestController
public class ProductController {
@Autowired
private ProductService productService;
@GetMapping("/findAllProductM")
public String findAllProductM() {
String key = "master";
RoutingDataSourceContext routingDataSourceContext = new RoutingDataSourceContext(key);
productService.findAllProductM();
return "master";
}
@GetMapping("/findAllProductS")
public String findAllProductS() {
String key = "slave";
RoutingDataSourceContext routingDataSourceContext = new RoutingDataSourceContext(key);
productService.findAllProductS();
return "slave";
}
}
9. 优化
(1)添加自定义注解
@Target(ElementType.METHOD)//该注解可以标注在方法上
@Retention(RetentionPolicy.RUNTIME)//设置生命周期
public @interface RoutingWith {
String value() default "master";
}
(2)导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
(3)编写切面类
@Aspect//切面类
@Component
public class RoutingAspect {
@Around("@annotation(with)")//环绕通知,@annotation表示标注了某个注解的所有方法
public Object routingWithDateSource(ProceedingJoinPoint joinPoint,RoutingWith with) throws Throwable {
String key = with.value();
RoutingDataSourceContext routingDataSourceContext = new RoutingDataSourceContext(key);
return joinPoint.proceed();
}
}
(4)controller方法上添加自定义注解
@RestController
public class ProductController {
@Autowired
private ProductService productService;
@RoutingWith("master")
@GetMapping("/findAllProductM")
public String findAllProductM() {
// String key = "master";
// RoutingDataSourceContext routingDataSourceContext = new RoutingDataSourceContext(key);
productService.findAllProductM();
return "master";
}
@RoutingWith("slave")
@GetMapping("/findAllProductS")
public String findAllProductS() {
// String key = "slave";
// RoutingDataSourceContext routingDataSourceContext = new RoutingDataSourceContext(key);
productService.findAllProductS();
return "slave";
}
}