Spring Boot整合MySQL集群读写分离实现

Spring Boot实现方式

调研:
读写分离要做的事情就是对于一条SQL该选择哪个数据库去执行,至于谁来做选择数据库这件事,主要有两种实现方式,分别为:
1.使用中间件,比如Atlas,cobar,TDDL,mycat,heisenberg,Oceanus,vitess,OneProxy等
2.使用程序自己实现,利用Spring Boot提供的路由数据源以及AOP,实现起来简单快捷
验证:使用第二种方式Spring Boot数据源路由+AOP

代码实现
1.首先配置下pom.xml

    <dependencies>
        <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>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
    </dependencies>

2.数据源路由类功能RoutingDataSource.java

package com.example.demo.databases;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * 数据源路由类功能
 * @author fansongsong
 * @since 2020-09-08
 * @version 1.0
 */
public class RoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DBContext.get();
    }
}

3.数据源上下文类DBContext.java

package com.example.demo.databases;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * 数据源上下文类
 * @author fansongsong
 * @since 2020-09-08
 * @version 1.0
 */
@Slf4j
public class DBContext {
    private static final ThreadLocal<DBTypeEnum> dbContext = new ThreadLocal<>();

    private static final AtomicInteger counter = new AtomicInteger(-1);

    public static void set(DBTypeEnum dbType) {
        dbContext.set(dbType);
    }

    public static DBTypeEnum get() {
        return dbContext.get();
    }

    public static void master() {
        set(DBTypeEnum.MASTER);
        log.info("切换到master库");
    }

    public static void slave() {
        //  读库负载均衡(轮询方式)
        int index = counter.getAndIncrement() % 2;
        log.info("slave库访问线程数==>{}", counter.get());
        if (index == 0) {
            set(DBTypeEnum.SLAVE1);
            log.info("切换到slave1库");
        } else {
            set(DBTypeEnum.SLAVE2);
            log.info("切换到slave2库");
        }
    }
}

4.数据库枚举类DBTypeEnum.java

package com.example.demo.databases;

/**
 * 数据库枚举类
 * @author fansongsong
 * @since 2020-09-08
 * @version 1.0
 */
public enum DBTypeEnum {
    MASTER, SLAVE1, SLAVE2
}

这里我们配置三个库,分别是一个写库Master,2个读库slave1,slave2


package com.example.demo.databases;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
 * 数据库配置类
 * @author fansongsong
 * @since 2020-09-08
 * @version 1.0
 */
@Configuration
public class DataSourceConfigs {

    @Bean
    @ConfigurationProperties("spring.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.slave1")
    public DataSource slave1DataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.slave2")
    public DataSource slave2DataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    public DataSource myRoutingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
                                          @Qualifier("slave1DataSource") DataSource slave1DataSource,
                                          @Qualifier("slave2DataSource") DataSource slave2DataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DBTypeEnum.MASTER, masterDataSource);
        targetDataSources.put(DBTypeEnum.SLAVE1, slave1DataSource);
        targetDataSources.put(DBTypeEnum.SLAVE2, slave2DataSource);
        RoutingDataSource routingDataSource = new RoutingDataSource();
        routingDataSource.setDefaultTargetDataSource(masterDataSource);
        routingDataSource.setTargetDataSources(targetDataSources);
        return routingDataSource;
    }
}

6.mybatis配置类DataSourceConfigs.java

package com.example.demo.databases;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
 * 数据库配置类
 * @author fansongsong
 * @since 2020-09-08
 * @version 1.0
 */
@Configuration
public class DataSourceConfigs {

    @Bean
    @ConfigurationProperties("spring.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.slave1")
    public DataSource slave1DataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.slave2")
    public DataSource slave2DataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    public DataSource myRoutingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
                                          @Qualifier("slave1DataSource") DataSource slave1DataSource,
                                          @Qualifier("slave2DataSource") DataSource slave2DataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DBTypeEnum.MASTER, masterDataSource);
        targetDataSources.put(DBTypeEnum.SLAVE1, slave1DataSource);
        targetDataSources.put(DBTypeEnum.SLAVE2, slave2DataSource);
        RoutingDataSource routingDataSource = new RoutingDataSource();
        routingDataSource.setDefaultTargetDataSource(masterDataSource);
        routingDataSource.setTargetDataSources(targetDataSources);
        return routingDataSource;
    }
}

7.切面类DataSourceAop.java

package com.example.demo.databases;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * 切面类DataSourceAop
 * @author fansongsong
 * @since 2020-09-08
 * @version 1.0
 */
@Aspect
@Component
public class DataSourceAop {
    @Pointcut("@annotation(com.example.demo.databases.Master) " +
            "|| execution(* com.example.demo.*.service..*.insert*(..)) " +
            "|| execution(* com.example.demo.*.service..*.create*(..)) " +
            "|| execution(* com.example.demo.*.service..*.save*(..)) " +
            "|| execution(* com.example.demo.*.service..*.add*(..)) " +
            "|| execution(* com.example.demo.*.service..*.update*(..)) " +
            "|| execution(* com.example.demo.*.service..*.edit*(..)) " +
            "|| execution(* com.example.demo.*.service..*.delete*(..)) " +
            "|| execution(* com.example.demo.*.service..*.remove*(..))")
    public void writePointcut() {

    }

    @Pointcut("!@annotation(com.example.demo.databases.Master) " +
            "&& (execution(* com.example.demo.*.service..*.select*(..)) " +
            "|| execution(* com.example.demo.*.service..*.list*(..))" +
            "|| execution(* com.example.demo.*.service..*.count*(..))" +
            "|| execution(* com.example.demo.*.service..*.get*(..)))"

    )
    public void readPointcut() {

    }

    @Before("writePointcut()")
    public void write() {
        DBContext.master();
    }

    @Before("readPointcut()")
    public void read() {
        DBContext.slave();
    }
}

8.注解类Master.java

package com.example.demo.databases;

/**
 * 注解类Master 主库,可读写
 * @author fansongsong
 * @since 2020-09-08
 * @version 1.0
 */
public @interface Master {
}

9,验证
请求查询接口
在这里插入图片描述
控制台输出
在这里插入图片描述
指定数据源写库(查询默认读库,可以通过@Master注解加到方法上手动指定写库)

在这里插入图片描述
控制台输出
在这里插入图片描述
请求新增方法
在这里插入图片描述
请求批量新增用户接口

在这里插入图片描述
查看数据源切换到主库

在这里插入图片描述
在这里插入图片描述
验证成功

  • 0
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值