springboot separate read and write multiple data sources embodiment AOP

Hello everyone, I am a duck:

        Share springboot separate read and write configuration today.

         surroundings:

                 springboot  2.1.0.RELEASE

         Scene shows that the current demand is reading data sources * 2 * 1 + write data source

 

1. Profiles


    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. Configure class


 DataSourceConfig.java

 The default read data source, if the data source needs to be increased or decreased to modify the parameters of the method 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

Note that the file path is mapped mapper modified here, since re-injected sqlSession, invalid yml configured

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 configuration class, which limit the service by connecting aop ways which data source
the current annotation is based on the class judgment, which can be modified according to the data source is determined to take the annotated method

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

Data source enumeration, increase and decrease data sources can be modified

public enum DBTypeEnum {

    READ_AW, READ_DC, WRITE;

}

DBContextHolder.java

Data source switching classes, which holds the current thread-bound data source

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

Routing multiple data sources

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

Annotation data source, applied to the serivice implementation classes specified value, AOP acquired annotation in accordance with the specified data source.


package com.test.test.datasource.annotation;

import java.lang.annotation.*;

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

For example in this embodiment, the default library is read READ_AW, if not the default annotation, reading default library. If you specify annotation READ_DC, to use the specified data source.

 

3. Test results

Fake code:

 

Using the query (different libraries) tes insert operation method, the results shown in FIG.

 

Published 115 original articles · won praise 58 · Views 230,000 +

Guess you like

Origin blog.csdn.net/Angry_Mills/article/details/89417115