SpringBoot读写分离组件开发详解

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

[[389704]]

环境:springboot2.2.6RELEASE

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

实现原理:通过aop。

1.pom.xml配置文件

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

 2.application.yml配置文件

pack: 
  datasource: 
    pointcut: execution(public * net.greatsoft.service.base.*.*(..)) || execution(public * net.greatsoft.service.xxx.*.*(..)) 
    master: 
      driverClassName: oracle.jdbc.driver.OracleDriver 
      jdbcUrl: jdbc:oracle:thin:@10.100.102.113:1521/orcl 
      username: test 
      password: test 
      minimumIdle: 10 
      maximumPoolSize: 200 
      autoCommit: true 
      idleTimeout: 30000 
      poolName: MbookHikariCP 
      maxLifetime: 1800000 
      connectionTimeout: 30000 
      connectionTestQuery: SELECT 1 FROM DUAL   
    slaves: 
      - driverClassName: oracle.jdbc.driver.OracleDriver 
        jdbcUrl: jdbc:oracle:thin:@10.100.102.113:1521/orcl 
        username: dc 
        password: dc 
        minimumIdle: 10 
        maximumPoolSize: 200 
        autoCommit: true 
        idleTimeout: 30000 
        poolName: MbookHikariCP 
        maxLifetime: 1800000 
        connectionTimeout: 30000 
        connectionTestQuery: SELECT 1 FROM DUAL 
      - driverClassName: oracle.jdbc.driver.OracleDriver 
        jdbcUrl: jdbc:oracle:thin:@10.100.102.113:1521/orcl 
        username: empi 
        password: empi 
        minimumIdle: 10 
        maximumPoolSize: 200 
        autoCommit: true 
        idleTimeout: 30000 
        poolName: MbookHikariCP 
        maxLifetime: 1800000 
        connectionTimeout: 30000 
        connectionTestQuery: SELECT 1 FROM DUAL 
  • 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.

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

master:写库配置。

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

3.属性配置类

@Component 
@ConfigurationProperties(prefix = "pack.datasource"
public class RWDataSourceProperties { 
     
    private String pointcut ; 
    private HikariConfig master ; 
    private List<HikariConfig> slaves = new ArrayList<>(); 
     

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

 4.读写配置类

public class RWConfig  { 
     
    private static Logger logger = LoggerFactory.getLogger(RWConfig.class) ; 
 
    @Bean 
    public HikariDataSource masterDataSource(RWDataSourceProperties rwDataSourceProperties) { 
        return new HikariDataSource(rwDataSourceProperties.getMaster()) ; 
    } 
     
    @Bean 
    public List<HikariDataSource> slaveDataSources(RWDataSourceProperties rwDataSourceProperties) { 
        List<HikariDataSource> lists = new ArrayList<>() ; 
        for(HikariConfig config : rwDataSourceProperties.getSlaves()) { 
            lists.add(new HikariDataSource(config)) ; 
        } 
        return lists ; 
    } 
     
    @Bean 
  @Primary 
    @DependsOn({"masterDataSource""slaveDataSources"}) 
    public AbstractRoutingDataSource routingDataSource(@Qualifier("masterDataSource")DataSource masterDataSource, 
            @Qualifier("slaveDataSources")List<HikariDataSource> slaveDataSources) { 
        BaseRoutingDataSource ds = new BaseRoutingDataSource() ; 
        Map<Object, Object> targetDataSources = new HashMap<>(2) ; 
        targetDataSources.put("master", masterDataSource) ; 
        for (int i = 0; i < slaveDataSources.size(); i++) { 
            targetDataSources.put("slave-" + i, slaveDataSources.get(i)) ; 
        } 
        ds.setDefaultTargetDataSource(masterDataSource) ; 
        ds.setTargetDataSources(targetDataSources) ; 
        return ds ; 
    } 
     

  • 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.

 5.数据源路由

public class BaseRoutingDataSource extends AbstractRoutingDataSource { 
 
    @Resource 
    private DataSourceHolder holder; 
     
    @Override 
    protected Object determineCurrentLookupKey() { 
        return holder.get() ; 
    } 
     

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

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.

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

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

public interface BaseSlaveLoad { 
 
    int load() ; 
     

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

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

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

public class PollingLoad extends AbstractSlaveLoad { 
     
    private int index = 0 ; 
    private int size = 1 ; 
     
    @PostConstruct 
    public void init() { 
        size = slaveDataSources.size() ; 
    } 
     
    @Override 
    public int load() { 
        int n = index ; 
        synchronized (this) { 
            index = (++index) % size ; 
        } 
        return n ; 
    } 
     

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.

 配置成Bean

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

 6.数据源AOP

public class DataSourceAspect implements MethodInterceptor { 
 
    private DataSourceHolder holder ; 
     
    public DataSourceAspect(DataSourceHolder holder) { 
        this.holder = holder ; 
    } 
     
    @Override 
    public Object invoke(MethodInvocation invocation) throws Throwable { 
        Method method = invocation.getMethod() ; 
        String methodName = method.getName() ; 
        SlaveDB slaveDB = method.getAnnotation(SlaveDB.class) ; 
        if (slaveDB == null) { 
            slaveDB = method.getDeclaringClass().getAnnotation(SlaveDB.class) ; 
        } 
        if (methodName.startsWith("find")  
                || methodName.startsWith("get"
                || methodName.startsWith("query"
                || methodName.startsWith("select"
                || methodName.startsWith("list"
                || slaveDB != null) { 
            holder.set(1) ; 
        } else { 
            holder.set(0) ; 
        } 
        return invocation.proceed(); 
    } 
 

  • 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 aop的方式来配置

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

 7.Enable开启功能

public class RWImportSelector implements ImportSelector { 
 
    @Override 
    public String[] selectImports(AnnotationMetadata importingClassMetadata) { 
        return new String[] {RWConfig.class.getName()} ; 
    } 
 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

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

@Retention(RetentionPolicy.RUNTIME) 
@Target(ElementType.TYPE) 
@Documented 
@Import({RWImportSelector.class}) 
public @interface EnableRW { 

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

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

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

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

8.打包安装到本地仓库

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

9.新建base-web项目

引入依赖

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

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

@SpringBootApplication 
@EnableRW 
public class BaseWebApplication { 
 
    public static void main(String[] args) { 
        SpringApplication.run(BaseWebApplication.class, args); 
    } 
 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

 测试:

第一次查询:

第二次查询:

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

这里是写库

完毕!!!

 

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

2023-07-07 08:36:45

配置注解jar

2020-04-23 15:08:41

SpringBootMyCatJava

2021-06-25 10:05:58

SpringBootMySQL数据库

2024-05-31 13:34:57

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

数据库读写分离

2011-08-10 16:27:07

Cocoa TouchPlist

2009-04-10 09:06:16

Windows Emb

2017-05-25 10:22:13

NoSQL数据库主主备份

2022-04-25 08:03:57

MySQL中间件MyCat

2010-05-17 11:19:44

MySQL proxy

2009-05-04 09:13:52

PHPMySQL读写分离

2017-09-04 09:53:58

MySQLAtlasNavicat

2018-01-01 05:23:13

服务化读写分离架构

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数据库
点赞
收藏

51CTO技术栈公众号