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

Spring Boot实现方式

读写分离要做的事情就是对于一条SQL该选择哪个数据库去执行,至于谁来做选择数据库这件事儿,一般来讲,主要有两种实现方式,分别为:

1.使用中间件,比如Atlas,cobar,TDDL,mycat,heisenberg,Oceanus,vitess,OneProxy等

2.使用程序自己实现,利用Spring Boot提供的路由数据源以及AOP,实现起来简单快捷(本文要介绍的方法)

代码实现

1.首先配置下pom.xml

   

       

            org.springframework.boot

            spring-boot-starter-web

       

       

            org.springframework.boot

            spring-boot-starter-test

            test

       

       

            mysql

            mysql-connector-java

            runtime

       

       

            org.springframework.boot

            spring-boot-starter-jdbc

       

       

            org.projectlombok

            lombok

            true

       

       

            org.mybatis.spring.boot

            mybatis-spring-boot-starter

            2.1.0

       

       

            org.springframework.boot

            spring-boot-starter-aop

       

   

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 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 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 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注解加到方法上手动指定写库)

请求新增方法

请求批量新增用户接口

查看数据源切换到主库

验证数据主从同步成功

你可能感兴趣的:(Spring Boot MyBatis 数据库集群读写分离实现验证)