Spring Boot MyBatis 数据库集群读写分离实现验证

Spring Boot实现方式
读写分离要做的事情就是对于一条SQL该选择哪个数据库去执行,至于谁来做选择数据库这件事儿,一般来讲,主要有两种实现方式,分别为:
  • 1.使用中间件,比如Atlas,cobar,TDDL,mycat,heisenberg,Oceanus,vitess,OneProxy等
  • 2.使用程序自己实现,利用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
5.数据库配置类DataSourceConfig.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;
    }
}
 
 
 
 
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注解加到方法上手动指定写库)
 
 
 
  • 请求新增方法
 
 
  • 请求批量新增用户接口
  • 查看数据源切换到主库
  • 验证数据主从同步成功
 
 
posted @ 2020-09-10 11:33  柠檬冰块  阅读(100)  评论(0)    收藏  举报