SpringBoot读写分离组件开发详解

开发 前端
本篇详细介绍关于SpringBoot读写分离组件开发的相关内容,一写多读,读可以任意配置多个,默认都是从写库中进行操作,只有符合条件的方法(指定的目标方法或者标有指定注解的方法才会从读库中操作)。独立打成一个jar包放入本地仓库。

[[389704]]

环境:springboot2.2.6RELEASE

实现目标:一写多读,读可以任意配置多个,默认都是从写库中进行操作,只有符合条件的方法(指定的目标方法或者标有指定注解的方法才会从读库中操作)。独立打成一个jar包放入本地仓库。

实现原理:通过aop。

1.pom.xml配置文件

  1. <dependency> 
  2.             <groupId>org.springframework.boot</groupId> 
  3.             <artifactId>spring-boot-starter</artifactId> 
  4. </dependency> 
  5. <dependency> 
  6.             <groupId>org.springframework.boot</groupId> 
  7.             <artifactId>spring-boot-starter-data-jpa</artifactId> 
  8. </dependency> 
  9. <dependency> 
  10.             <groupId>org.springframework.boot</groupId> 
  11.             <artifactId>spring-boot-configuration-processor</artifactId> 
  12.             <optional>true</optional> 
  13. </dependency> 

 2.application.yml配置文件

  1. pack: 
  2.   datasource: 
  3.     pointcut: execution(public * net.greatsoft.service.base.*.*(..)) || execution(public * net.greatsoft.service.xxx.*.*(..)) 
  4.     master: 
  5.       driverClassName: oracle.jdbc.driver.OracleDriver 
  6.       jdbcUrl: jdbc:oracle:thin:@10.100.102.113:1521/orcl 
  7.       username: test 
  8.       password: test 
  9.       minimumIdle: 10 
  10.       maximumPoolSize: 200 
  11.       autoCommit: true 
  12.       idleTimeout: 30000 
  13.       poolName: MbookHikariCP 
  14.       maxLifetime: 1800000 
  15.       connectionTimeout: 30000 
  16.       connectionTestQuery: SELECT 1 FROM DUAL   
  17.     slaves: 
  18.       - driverClassName: oracle.jdbc.driver.OracleDriver 
  19.         jdbcUrl: jdbc:oracle:thin:@10.100.102.113:1521/orcl 
  20.         username: dc 
  21.         password: dc 
  22.         minimumIdle: 10 
  23.         maximumPoolSize: 200 
  24.         autoCommit: true 
  25.         idleTimeout: 30000 
  26.         poolName: MbookHikariCP 
  27.         maxLifetime: 1800000 
  28.         connectionTimeout: 30000 
  29.         connectionTestQuery: SELECT 1 FROM DUAL 
  30.       - driverClassName: oracle.jdbc.driver.OracleDriver 
  31.         jdbcUrl: jdbc:oracle:thin:@10.100.102.113:1521/orcl 
  32.         username: empi 
  33.         password: empi 
  34.         minimumIdle: 10 
  35.         maximumPoolSize: 200 
  36.         autoCommit: true 
  37.         idleTimeout: 30000 
  38.         poolName: MbookHikariCP 
  39.         maxLifetime: 1800000 
  40.         connectionTimeout: 30000 
  41.         connectionTestQuery: SELECT 1 FROM DUAL 

 pointcut:定义切点,那些方法是需要拦截(从读库中操作)。

master:写库配置。

slaves:读库配置(List集合)。

3.属性配置类

  1. @Component 
  2. @ConfigurationProperties(prefix = "pack.datasource"
  3. public class RWDataSourceProperties { 
  4.      
  5.     private String pointcut ; 
  6.     private HikariConfig master ; 
  7.     private List<HikariConfig> slaves = new ArrayList<>(); 
  8.      

 4.读写配置类

  1. public class RWConfig  { 
  2.      
  3.     private static Logger logger = LoggerFactory.getLogger(RWConfig.class) ; 
  4.  
  5.     @Bean 
  6.     public HikariDataSource masterDataSource(RWDataSourceProperties rwDataSourceProperties) { 
  7.         return new HikariDataSource(rwDataSourceProperties.getMaster()) ; 
  8.     } 
  9.      
  10.     @Bean 
  11.     public List<HikariDataSource> slaveDataSources(RWDataSourceProperties rwDataSourceProperties) { 
  12.         List<HikariDataSource> lists = new ArrayList<>() ; 
  13.         for(HikariConfig config : rwDataSourceProperties.getSlaves()) { 
  14.             lists.add(new HikariDataSource(config)) ; 
  15.         } 
  16.         return lists ; 
  17.     } 
  18.      
  19.     @Bean 
  20.   @Primary 
  21.     @DependsOn({"masterDataSource""slaveDataSources"}) 
  22.     public AbstractRoutingDataSource routingDataSource(@Qualifier("masterDataSource")DataSource masterDataSource, 
  23.             @Qualifier("slaveDataSources")List<HikariDataSource> slaveDataSources) { 
  24.         BaseRoutingDataSource ds = new BaseRoutingDataSource() ; 
  25.         Map<Object, Object> targetDataSources = new HashMap<>(2) ; 
  26.         targetDataSources.put("master", masterDataSource) ; 
  27.         for (int i = 0; i < slaveDataSources.size(); i++) { 
  28.             targetDataSources.put("slave-" + i, slaveDataSources.get(i)) ; 
  29.         } 
  30.         ds.setDefaultTargetDataSource(masterDataSource) ; 
  31.         ds.setTargetDataSources(targetDataSources) ; 
  32.         return ds ; 
  33.     } 
  34.      

 5.数据源路由

  1. public class BaseRoutingDataSource extends AbstractRoutingDataSource { 
  2.  
  3.     @Resource 
  4.     private DataSourceHolder holder; 
  5.      
  6.     @Override 
  7.     protected Object determineCurrentLookupKey() { 
  8.         return holder.get() ; 
  9.     } 
  10.      

  1. public class DataSourceHolder { 
  2.      
  3.     private ThreadLocal<Integer> context = new ThreadLocal<Integer>() { 
  4.         @Override 
  5.         protected Integer initialValue() { 
  6.             return 0 ; 
  7.         } 
  8.     }; 
  9.      
  10.     @Resource 
  11.     private BaseSlaveLoad slaveLoad ; 
  12.      
  13.     public String get() { 
  14.         Integer type = context.get() ; 
  15.         return type == null || type == 0 ? "master" : "slave-" + slaveLoad.load() ; 
  16.     } 
  17.      
  18.     public void set(Integer type) { 
  19.         context.set(type) ; 
  20.     } 
  21.      

 通过aop动态设置context的内容值,0为从写库中操作,其它的都在读库中操作。

BaseSlaveLoad类为到底从那个读库中选取的一个算法类,默认实现使用的是轮询算法。

  1. public interface BaseSlaveLoad { 
  2.  
  3.     int load() ; 
  4.      

  1. public abstract class AbstractSlaveLoad implements BaseSlaveLoad { 
  2.  
  3.     @Resource 
  4.     protected List<HikariDataSource> slaveDataSources ; 
  5.      

 这里定义一个抽象类注入了读库列表,所有的实现类从该类中继承即可。

  1. public class PollingLoad extends AbstractSlaveLoad { 
  2.      
  3.     private int index = 0 ; 
  4.     private int size = 1 ; 
  5.      
  6.     @PostConstruct 
  7.     public void init() { 
  8.         size = slaveDataSources.size() ; 
  9.     } 
  10.      
  11.     @Override 
  12.     public int load() { 
  13.         int n = index ; 
  14.         synchronized (this) { 
  15.             index = (++index) % size ; 
  16.         } 
  17.         return n ; 
  18.     } 
  19.      

 配置成Bean

  1. @Bean 
  2.     @ConditionalOnMissingBean 
  3.     public BaseSlaveLoad slaveLoad() { 
  4.         return new PollingLoad() ; 
  5.     } 
  6.      
  7.     @Bean 
  8.     public DataSourceHolder dataSourceHolder() { 
  9.         return new DataSourceHolder() ; 
  10.     } 

 6.数据源AOP

  1. public class DataSourceAspect implements MethodInterceptor { 
  2.  
  3.     private DataSourceHolder holder ; 
  4.      
  5.     public DataSourceAspect(DataSourceHolder holder) { 
  6.         this.holder = holder ; 
  7.     } 
  8.      
  9.     @Override 
  10.     public Object invoke(MethodInvocation invocation) throws Throwable { 
  11.         Method method = invocation.getMethod() ; 
  12.         String methodName = method.getName() ; 
  13.         SlaveDB slaveDB = method.getAnnotation(SlaveDB.class) ; 
  14.         if (slaveDB == null) { 
  15.             slaveDB = method.getDeclaringClass().getAnnotation(SlaveDB.class) ; 
  16.         } 
  17.         if (methodName.startsWith("find")  
  18.                 || methodName.startsWith("get"
  19.                 || methodName.startsWith("query"
  20.                 || methodName.startsWith("select"
  21.                 || methodName.startsWith("list"
  22.                 || slaveDB != null) { 
  23.             holder.set(1) ; 
  24.         } else { 
  25.             holder.set(0) ; 
  26.         } 
  27.         return invocation.proceed(); 
  28.     } 
  29.  

 应该切点需要动态配置,所以这里采用spring aop的方式来配置

  1. @Bean 
  2.     public AspectJExpressionPointcutAdvisor logAdvisor(RWDataSourceProperties props, DataSourceHolder holder) { 
  3.         AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor() ; 
  4.         logger.info("执行表达式:{}", props.getPointcut()) ; 
  5.         advisor.setExpression(props.getPointcut()) ; 
  6.         advisor.setAdvice(new DataSourceAspect(holder)) ; 
  7.         return advisor ; 
  8.     } 

 7.Enable开启功能

  1. public class RWImportSelector implements ImportSelector { 
  2.  
  3.     @Override 
  4.     public String[] selectImports(AnnotationMetadata importingClassMetadata) { 
  5.         return new String[] {RWConfig.class.getName()} ; 
  6.     } 
  7.  

 这里的RWConfig为我们上面的配置类

  1. @Retention(RetentionPolicy.RUNTIME) 
  2. @Target(ElementType.TYPE) 
  3. @Documented 
  4. @Import({RWImportSelector.class}) 
  5. public @interface EnableRW { 

  1. @Documented 
  2. @Retention(RUNTIME) 
  3. @Target({ TYPE, METHOD }) 
  4. public @interface SlaveDB { 

 有@SlaveDB的注解方法会类都会从读库中操作。

到此读写分离组件开发完成。

8.打包安装到本地仓库

  1. mvn install -Dmaven.test.skip=true 

9.新建base-web项目

引入依赖

  1. <dependency> 
  2.             <groupId>com.pack</groupId> 
  3.             <artifactId>xg-component-rw</artifactId> 
  4.             <version>1.0.0</version> 
  5. </dependency> 

 启动类添加注解开启读写分离功能

  1. @SpringBootApplication 
  2. @EnableRW 
  3. public class BaseWebApplication { 
  4.  
  5.     public static void main(String[] args) { 
  6.         SpringApplication.run(BaseWebApplication.class, args); 
  7.     } 
  8.  

 测试:

第一次查询:

第二次查询:

为了区别两个从库设置不同的数据

这里是写库

完毕!!!

 

责任编辑:姜华 来源: 今日头条
相关推荐

2023-07-07 08:36:45

配置注解jar

2020-04-23 15:08:41

SpringBootMyCatJava

2021-06-25 10:05:58

SpringBootMySQL数据库

2011-08-30 12:49:59

Mysql ProxyLua分离

2019-09-30 09:19:54

Redis分离云数据库

2011-08-22 12:01:38

iPhone开发文件

2018-10-16 16:45:05

数据库读写分离

2009-04-10 09:06:16

Windows Emb

2017-05-25 10:22:13

NoSQL数据库主主备份

2022-04-25 08:03:57

MySQL中间件MyCat

2017-09-04 09:53:58

MySQLAtlasNavicat

2010-05-17 11:19:44

MySQL proxy

2018-01-01 05:23:13

服务化读写分离架构

2009-05-04 09:13:52

PHPMySQL读写分离

2011-08-10 16:27:07

Cocoa TouchPlist

2009-09-16 13:05:32

C#组件开发

2020-02-28 19:06:21

缓存读写Redis

2023-02-01 07:34:41

读写分离数据库

2021-09-08 10:23:08

读写分离Java数据库

2022-06-20 14:59:14

读写分离模Loki
点赞
收藏

51CTO技术栈公众号