springboot 多数据源 读写分离 AOP方式

大家好,我是烤鸭:

        今天分享springboot读写分离配置。

         环境:

                 springboot  2.1.0.RELEASE

         场景说明,目前的需求是 读数据源 * 2 + 写数据源 * 1

1.    配置文件


    application.yml

server:
  port: 8085
spring:
  application:
    name: test-data-test
  datasource:
    write:
      jdbc-url: jdbc:mysql://localhost:3306/test
      username: root
      password: test.Dev
      driver-class-name: com.mysql.jdbc.Driver
      type: com.zaxxer.hikari.HikariDataSource   
      connectionTimeout: 30000
      validationTimeout: 5000
      maxPoolSize: 200
      minIdle: 100
    readaw:
      jdbc-url: jdbc:mysql://localhost:3306/test
      username: root
      password: test!i
      driver-class-name: com.mysql.jdbc.Driver
      type: com.zaxxer.hikari.HikariDataSource   
      connectionTimeout: 30000
      validationTimeout: 5000
      maxPoolSize: 200
      minIdle: 100
    readdc:
      jdbc-url: jdbc:mysql://localhost:3306/test
      username: root
      password: test!i
      driver-class-name: com.mysql.jdbc.Driver
      type: com.zaxxer.hikari.HikariDataSource   
      connectionTimeout: 30000
      validationTimeout: 5000
      maxPoolSize: 200
      minIdle: 100
#mybatis
mybatis:
  ###把xml文件放在com.XX.mapper.*中可能会出现找到的问题,这里把他放在resource下的mapper中
  mapper-mapperLocations: classpath*:mapper/**/**/*.xml
  type-aliases-package: com.test.test.pojo
  configuration:
    map-underscore-to-camel-case: true
    cache-enabled: false
    call-setters-on-nulls: true
    useGeneratedKeys: true

2.    配置类


 DataSourceConfig.java

 默认 读数据源,如果需要增加或者减少数据源需要修改 myRoutingDataSource 方法中的参数

package com.test.test.config.db;

import com.test.test.datasource.MyRoutingDataSource;
import com.test.test.datasource.enums.DBTypeEnum;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Autowired;
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 org.springframework.core.env.Environment;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * 关于数据源配置,参考SpringBoot官方文档第79章《Data Access》
 * 79. Data Access
 * 79.1 Configure a Custom DbSource
 * 79.2 Configure Two DataSources
 */

@Configuration
public class DataSourceConfig {

    @Autowired
    Environment environment;

    @Bean
    @ConfigurationProperties("spring.datasource.readaw")
    public DataSource readDataSourceAw() {
        DataSource build = DataSourceBuilder.create().build();
        HikariDataSource hikariDataSource = buildDataSource(build,"readaw");
        return hikariDataSource;
    }

    @Bean
    @ConfigurationProperties("spring.datasource.readdc")
    public DataSource readDataSourceDc() {
        DataSource build = DataSourceBuilder.create().build();
        HikariDataSource hikariDataSource = buildDataSource(build,"readdc");
        return hikariDataSource;
    }

    @Bean
    @ConfigurationProperties("spring.datasource.write")
    public DataSource writeDataSource() {
        DataSource build = DataSourceBuilder.create().build();
        HikariDataSource hikariDataSource = buildDataSource(build,"write");
        return hikariDataSource;
    }

    @Bean
    public DataSource myRoutingDataSource(@Qualifier("readDataSourceAw") DataSource readDataSourceAw,
                                          @Qualifier("readDataSourceDc") DataSource readDataSourceDc,
                                          @Qualifier("writeDataSource") DataSource writeDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DBTypeEnum.READ_AW, readDataSourceAw);
        targetDataSources.put(DBTypeEnum.READ_DC, readDataSourceDc);
        targetDataSources.put(DBTypeEnum.WRITE, writeDataSource);
        MyRoutingDataSource myRoutingDataSource = new MyRoutingDataSource();
        myRoutingDataSource.setDefaultTargetDataSource(readDataSourceAw);
        myRoutingDataSource.setTargetDataSources(targetDataSources);
        return myRoutingDataSource;
    }

    public HikariDataSource buildDataSource(DataSource dataSource,String dataSourcePrefix){
        HikariDataSource hikariDataSource= (HikariDataSource) dataSource;
        hikariDataSource.setDriverClassName(environment.getProperty("spring.datasource."+dataSourcePrefix+".driver-class-name"));
        hikariDataSource.setJdbcUrl(environment.getProperty("spring.datasource."+dataSourcePrefix+".jdbc-url"));
        hikariDataSource.setUsername(environment.getProperty("spring.datasource."+dataSourcePrefix+".username"));
        hikariDataSource.setPassword(environment.getProperty("spring.datasource."+dataSourcePrefix+".password"));
        hikariDataSource.setMinimumIdle(Integer.parseInt(environment.getProperty("spring.datasource."+dataSourcePrefix+".minIdle")));
        hikariDataSource.setConnectionTimeout(Long.parseLong(environment.getProperty("spring.datasource."+dataSourcePrefix+".connectionTimeout")));
        hikariDataSource.setValidationTimeout(Long.parseLong(environment.getProperty("spring.datasource."+dataSourcePrefix+".validationTimeout")));
        hikariDataSource.setMaximumPoolSize(Integer.parseInt(environment.getProperty("spring.datasource."+dataSourcePrefix+".maxPoolSize")));
        return hikariDataSource;
    }
}

MyBatisConfig.java

注意映射mapper文件路径是在这里修改的,因为重新注入了sqlSession, yml中配置的无效

package com.test.test.config.mybatis;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.annotation.Resource;
import javax.sql.DataSource;

@EnableTransactionManagement
@Configuration
public class MyBatisConfig {

    @Resource(name = "myRoutingDataSource")
    private DataSource myRoutingDataSource;

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(myRoutingDataSource);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/**/*.xml"));
        return sqlSessionFactoryBean.getObject();
    }

    @Bean
    public PlatformTransactionManager platformTransactionManager() {
        return new DataSourceTransactionManager(myRoutingDataSource);
    }
}

DataSourceAop.java

aop配置类,通过aop的方式限制哪个service的方法连接哪个数据源
目前是根据类上的注解来判断,可以修改为根据方法的注解来判断走哪个数据源

package com.test.test.datasource.aop;

import com.test.test.datasource.annotation.DbSource;
import com.test.test.datasource.handler.DBContextHolder;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect
@Component
public class DataSourceAop {
    /**
     * 另一种写法:if...else...  判断哪些需要读从数据库,其余的走主数据库
     */
    @Before("execution(* com.test.test.service.impl.*.*(..))")
    public void before(JoinPoint jp){
        MethodSignature methodSignature = (MethodSignature) jp.getSignature();
        Method method = methodSignature.getMethod();
        System.out.println("拦截到了" + jp.getSignature().getName() +"方法...");
        Class<?> targetClass = jp.getTarget().getClass();
        boolean flag = targetClass.isAnnotationPresent(DbSource.class);
        //包含数据源注解,数据源为注解中的类
        if(flag){
            //获取注解的value
            DbSource annotation = targetClass.getAnnotation(DbSource.class);
            String value = annotation.value();
            DBContextHolder.read(value);
        }else {
            //不包含注解,查询方法默认走 默认读数据源
            if (StringUtils.startsWithAny(method.getName(), "get", "select", "find")) {
                DBContextHolder.read("");
            }else {
                DBContextHolder.write();
            }
        }
    }
}

DBTypeEnum.java

数据源枚举,增加和减少数据源修改即可

public enum DBTypeEnum {

    READ_AW, READ_DC, WRITE;

}

DBContextHolder.java

数据源切换类,保持当前线程绑定哪个数据源

package com.test.test.datasource.handler;


import com.test.test.datasource.enums.DBTypeEnum;
import org.apache.commons.lang3.StringUtils;

import java.util.concurrent.atomic.AtomicInteger;
/**
 * @Author gmwang
 * @Description // 数据源切换类
 * @Date 2019/4/30 9:20
 * @Param
 * @return
 **/
public class DBContextHolder {
    private static final ThreadLocal<DBTypeEnum> contextHolder = new ThreadLocal<>();
    private static final AtomicInteger counter = new AtomicInteger(-1);

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

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

    public static void read(String value) {
        if(StringUtils.isBlank(value)){
            set(DBTypeEnum.READ_AW);
            System.out.println("切换到读"+DBTypeEnum.READ_AW.toString());
        }
        if (DBTypeEnum.READ_DC.toString().equals(value)){
            set(DBTypeEnum.READ_DC);
            System.out.println("切换到读"+DBTypeEnum.READ_DC.toString());
        }
    }
    public static void write() {
        set(DBTypeEnum.WRITE);
        System.out.println("切换到写"+DBTypeEnum.WRITE.toString());
    }
}

MyRoutingDataSource.java

多数据源的路由类

package com.test.test.datasource;

import com.test.test.datasource.handler.DBContextHolder;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.lang.Nullable;
/**
 * @Author gmwang
 * @Description //多数据源的路由
 * @Date 2019/4/30 9:38
 * @Param
 * @return
 **/
public class MyRoutingDataSource extends AbstractRoutingDataSource {
    /**
     * @Author gmwang
     * @Description //根据Key获取数据源的信息,上层抽象函数的钩子
     * @Date 2019/4/30 9:39
     * @Param []
     * @return java.lang.Object
     **/
    @Nullable
    @Override
    protected Object determineCurrentLookupKey() {
        return DBContextHolder.get();
    }
}

DbSource

数据源注解,加在serivice实现类上,指定 value,AOP根据注解获取指定的数据源。


package com.test.test.datasource.annotation;

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface DbSource {
    String value();
}

例如本例中,默认读库是  READ_AW ,如果不加注解默认,读取默认库。如果指定注解 READ_DC,就用指定的数据源。

3.    结果测试

伪代码:

在tes方法中使用查询(不同的库)后插入操作,结果如图所示。

发布了115 篇原创文章 · 获赞 58 · 访问量 23万+

猜你喜欢

转载自blog.csdn.net/Angry_Mills/article/details/89417115
今日推荐