首页 > 编程语言 > 详细

SpringBoot-主从动态数据源

时间:2020-09-05 23:45:43      阅读:185      评论:0      收藏:0      [点我收藏+]

1、背景

在实际的项目中,一般一个项目都会有主数据库和从数据库,主从数据库之间的数据同步是通过数据库的配置来完成的,一般地这个工作都是由DBA来进行完成。但是,如果我们的项目中的业务量比较大的时候,我们希望读操作从数据库中读取数据,写操作的时候才将数据保存至主数据库,然后主数据库和从数据库之间通过通信将数据完成同步;那么,我们的程序是如何将做到读操作的时候从从库中读取数据,写操作的时候是如何将数据写入到主库的呢?这个问题,就是今天要解决的问题;

目前市面上实现主从数据源切换的方式主要有两种,一种是利用第三方插件的形式实现,另外一种就是通过使用AOP进行实现。我采用的实现方式就是利用SpringAOP的方式实现;

 2、实现

2.1 导入所需要的依赖包

我的项目使用的SpringBoot实现的,ORM框架使用的是Mybatis,数据源使用的是阿里的Druid。配置如下:

 1 <!--springBoot-->
 2 <dependency>
 3     <groupId>org.springframework.boot</groupId>
 4     <artifactId>spring-boot-starter-web</artifactId>
 5 </dependency>
 6 <dependency>
 7     <groupId>org.springframework.boot</groupId>
 8     <artifactId>spring-boot-starter-aop</artifactId>
 9 </dependency>
10 <!--mybatis-->
11 <dependency>
12     <groupId>org.mybatis.spring.boot</groupId>
13     <artifactId>mybatis-spring-boot-starter</artifactId>
14     <version>2.1.3</version>
15 </dependency>
16 <!--mysql-->
17 <dependency>
18     <groupId>mysql</groupId>
19     <artifactId>mysql-connector-java</artifactId>
20     <scope>runtime</scope>
21 </dependency>
22 <!--druid-->
23 <dependency>
24     <groupId>com.alibaba</groupId>
25     <artifactId>druid-spring-boot-starter</artifactId>
26     <version>1.1.10</version>
27 </dependency>
28 <!--lombok-->
29 <dependency>
30     <groupId>org.projectlombok</groupId>
31     <artifactId>lombok</artifactId>
32     <optional>true</optional>
33 </dependency>

2.2 配置数据源

  为了节约成本,我只在本地的计算机上进行了代码实现,所以我只是在本地的同一个mysql服务上配置了多个数据库,数据库之间也没有进行主从的配置,毕竟我的主要目的是想看看代码的实现效果;配置文件如下:

 1 # 主库
 2 spring.datasource.master.name=master
 3 spring.datasource.master.driver-class-name=com.mysql.jdbc.Driver
 4 spring.datasource.master.url=jdbc:mysql://localhost:3306/master?serverTimezone=UTC&useSSL=false
 5 spring.datasource.master.username=root
 6 spring.datasource.master.password=root
 7 # 从库1
 8 spring.datasource.slaver1.name=slaver1
 9 spring.datasource.slaver1.driver-class-name=com.mysql.jdbc.Driver
10 spring.datasource.slaver1.url=jdbc:mysql://localhost:3306/slaver1?serverTimezone=UTC&useSSL=false
11 spring.datasource.slaver1.username=root
12 spring.datasource.slaver1.password=root
13 # 从库2
14 spring.datasource.slaver2.name=slaver1
15 spring.datasource.slaver2.driver-class-name=com.mysql.jdbc.Driver
16 spring.datasource.slaver2.url=jdbc:mysql://localhost:3306/slaver2?serverTimezone=UTC&useSSL=false
17 spring.datasource.slaver2.username=root
18 spring.datasource.slaver2.password=root

2.3 业务操作层的实现

数据的操作需要借助Service层和Dao层的进行实现,由于这部分不是实现主从数据源的关键部分,所以此处的代码就不进行展示;

2.4 数据源配置

我们都知道,Spring和Mybatis在整合的时候都需要配置 org.mybatis.spring.SqlSessionFactoryBean 的实例,在配置这个实例的时候需要指定数据源。那么如果想要实现主从数据源动态切换的功能,这个数据源的配置就不能使用传统的DataSource了,这里我是用的是 org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource 数据源。这个数据源是Spring提供的,它可以在获取数据源连接之前通过方法 determineTargetDataSource() 判断获取哪一个数据源的连接;也正是因为这个特性,我们才得以实现数据源动态切换的功能;

数据源配置如下:

 1 import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
 2 import com.example.demo.config.db.DataSourceRoutingDataSource;
 3 import org.mybatis.spring.SqlSessionFactoryBean;
 4 import org.springframework.boot.context.properties.ConfigurationProperties;
 5 import org.springframework.context.annotation.Bean;
 6 import org.springframework.context.annotation.Configuration;
 7 import org.springframework.context.annotation.Primary;
 8 import org.springframework.core.io.Resource;
 9 import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
10 import org.springframework.stereotype.Component;
11 
12 import javax.sql.DataSource;
13 import java.io.IOException;
14 import java.util.HashMap;
15 import java.util.HashSet;
16 import java.util.Map;
17 import java.util.Set;
18 
19 /**
20  * 数据源配置文件
21  */
22 @Component
23 @Configuration
24 public class DatasourceConfig {
25 
26     /**
27      * 创建一个主数据源的实例
28      */
29     @Primary
30     @Bean(value = "master")
31     @ConfigurationProperties(prefix = "spring.datasource.master")
32     public DataSource master() {
33         return DruidDataSourceBuilder.create().build();
34     }
35 
36     /**
37      * 从数据源1
38      */
39     @Bean(value = "slaver1")
40     @ConfigurationProperties(prefix = "spring.datasource.slaver1")
41     public DataSource slaver1() {
42         return DruidDataSourceBuilder.create().build();
43     }
44 
45     /**
46      * 从数据源2
47      */
48     @Bean(value = "slaver2")
49     @ConfigurationProperties(prefix = "spring.datasource.slaver2")
50     public DataSource slaver2() {
51         return DruidDataSourceBuilder.create().build();
52     }
53 
54     /**
55      * DataSourceRoutingDataSource 继承了 AbstractRoutingDataSource;
56      *  主要为了实现determineCurrentLookupKey()方法;
57      */
58     @Bean(value = "dataSource")
59     public DataSourceRoutingDataSource dataSource() {
60         DataSourceRoutingDataSource dataSource = new DataSourceRoutingDataSource();
61         // 数据源
62         Map<Object, Object> dataSources = new HashMap<>();
63         dataSources.put("master", master());
64         dataSources.put("slaver1", slaver1());
65         dataSources.put("slaver2", slaver2());
66         dataSource.setTargetDataSources(dataSources);
67         dataSource.setDefaultTargetDataSource(master());
68         // 设置主数据源的键值;
69         Set<Object> masterKeys = new HashSet<>();
70         masterKeys.add("master");
71         dataSource.setMasterKeys(masterKeys);
72         // 设置从数据源的键值;
73         Set<Object> slaverKeys = new HashSet<>();
74         slaverKeys.add("slaver1");
75         slaverKeys.add("slaver2");
76         dataSource.setSlaverKeys(slaverKeys);
77 
78         return dataSource;
79     }
80 
81     /**
82      * SqlSessionFactoryBean实例配置
83      */
84     @Bean
85     public SqlSessionFactoryBean sqlSessionFactoryBean() throws IOException {
86         SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
87         factoryBean.setDataSource(dataSource());
88 
89         PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
90         Resource[] resources = resolver.getResources("classpath*:mapper/*.xml");
91         factoryBean.setMapperLocations(resources);
92         factoryBean.setTypeAliasesPackage("com.example.demo.domain");
93         return factoryBean;
94     }
95 }

DataSourceRoutingDataSource实现如下:

 1 import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 import java.util.Set;
 6 import java.util.concurrent.atomic.AtomicBoolean;
 7 import java.util.concurrent.atomic.AtomicInteger;
 8 
 9 public class DataSourceRoutingDataSource extends AbstractRoutingDataSource {
10 
11     public static AtomicBoolean MASTER_STATUS = new AtomicBoolean(true);
12 
13     private static List<Object> MASTER_KEYS = new ArrayList<>();
14     private static AtomicInteger MASTER_INDEX = new AtomicInteger(0);
15     private static List<Object> SLAVER_KEYS = new ArrayList<>();
16     private static AtomicInteger SLAVER_INDEX = new AtomicInteger(0);
17 
18     /*
19     * 关键点:用于切换数据源
20     * */
21     @Override
22     protected Object determineCurrentLookupKey() {
23         if (MASTER_STATUS.get()) {
24             return getNextMaster();
25         } else {
26             return getNextSlaver();
27         }
28     }
29 
30     public void setMasterKeys(Set<Object> masterKeys) {
31         MASTER_KEYS.addAll(masterKeys);
32     }
33 
34     public void setSlaverKeys(Set<Object> slaverKeys) {
35         SLAVER_KEYS.addAll(slaverKeys);
36     }
37 
38     /**
39      * 获取下一个主库的key
40      */
41     private Object getNextMaster() {
42         if (MASTER_KEYS.size() == 1) {
43             return MASTER_KEYS.get(0);
44         }
45         int index = MASTER_INDEX.getAndAdd(1);
46         return MASTER_KEYS.get(index % MASTER_KEYS.size());
47     }
48 
49     /**
50      * 获取下一个从库的key
51      */
52     private Object getNextSlaver() {
53         if (SLAVER_KEYS.size() == 1) {
54             return SLAVER_KEYS.get(0);
55         }
56         int index = SLAVER_INDEX.getAndAdd(1);
57         return SLAVER_KEYS.get(index % SLAVER_KEYS.size());
58     }
59 }

 

2.5 AOP配置

实现上面的步骤其实已经可以进行增删改查的功能了,但是我们目的不在此;我们还要通过AOP进行数据源的切换,所以我们还需要配置AOP;我这里写的比较简单,就是根据service的名称判断是否使用主库;代码如下:

 1 import com.example.demo.config.db.DataSourceRoutingDataSource;
 2 import org.aspectj.lang.JoinPoint;
 3 import org.aspectj.lang.annotation.Aspect;
 4 import org.aspectj.lang.annotation.Before;
 5 import org.aspectj.lang.annotation.Pointcut;
 6 import org.springframework.stereotype.Component;
 7 
 8 @Aspect
 9 @Component
10 public class ServiceAspect {
11 
12     @Pointcut(value = "execution(* com.example.demo.service.*.*(..))")
13     public void point() {}
14 
15     @Before(value = "point()")
16     public void before(JoinPoint joinPoint) {
17         String name = joinPoint.getSignature().getName();
18         if (name.startsWith("get") || name.startsWith("query") || name.startsWith("find")) {
19             DataSourceRoutingDataSource.MASTER_STATUS.set(false);
20         } else {
21             DataSourceRoutingDataSource.MASTER_STATUS.set(true);
22         }
23     }
24 }

3、总结

动态数据源的实现方式还是比较简单的,核心就在于配置数据源为 org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource 类型的数据源;如果感兴趣的话,可以看一看内部的实现源码;

项目源码:https://gitee.com/chao_actor/cnblogs.git

 

 

 

 

 

SpringBoot-主从动态数据源

原文:https://www.cnblogs.com/chao-actor/p/13619797.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!