Springboot動的切替データソース+jpa

6518 ワード

1、アプリケーション-dev.properties構成
spring.datasource.primary.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.primary.jdbc-url=jdbc:mysql:///project_datahub?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
spring.datasource.primary.username=root
spring.datasource.primary.password=root

spring.datasource.secondary.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.secondary.jdbc-url=jdbc:mysql:///project_datahub_second?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
spring.datasource.secondary.username=root
spring.datasource.secondary.password=root

2、異なる要求、異なるスレッド、データソースの隔離クラス
/**
 * @Description      
 * @Author lxk
 * @version V1.0.0
 */
public class DBContextHolder{

    /**
     * ThreadLocal    ,         ,                   ,               
     */
    private static final ThreadLocal CONTEXT_HOLDER = new ThreadLocal<>();

    public static void setDataSource(String dataSource) {
        CONTEXT_HOLDER.set(dataSource);
    }

    public static String getDataSource() {
        return CONTEXT_HOLDER.get();
    }

    public static void clearDataSource() {
        CONTEXT_HOLDER.remove();
    }

}

3、spring公式AbstractRoutingDataSourceコアクラス実現
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.lang.Nullable;

public class MyRoutingDataSource extends AbstractRoutingDataSource {

    @Nullable
    @Override
    protected Object determineCurrentLookupKey() {
        return DBContextHolder.getDataSource();
    }

}

4、定数配置
/**
 * @Description
 * @Author lxk
 * @version V1.0.0
 */
public interface DataSourceNames {

    String FIRST = "first";

    String SECOND = "second";

}

5、データソースの構成
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.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

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

/**
 * @Description
 * @Author lxk
 * @version V1.0.0
 */
@Configuration
public class DataSourceConfig {

    @Bean(name = "firstDataSource")
    @Qualifier("firstDataSource")
    @ConfigurationProperties(prefix="spring.datasource.primary")
    public DataSource firstDataSource() {
        System.out.println("primary db built");
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "secondDataSource")
    @Qualifier("secondDataSource")
    @ConfigurationProperties(prefix="spring.datasource.secondary")
    public DataSource secondDataSource() {
        System.out.println("secondary db built");
        return DataSourceBuilder.create().build();
    }


    @Primary
    @Bean
    public DataSource dataSource(DataSource firstDataSource, DataSource secondDataSource) {
        Map targetDataSources = new HashMap<>(5);
        targetDataSources.put(DataSourceNames.FIRST, firstDataSource);
        targetDataSources.put(DataSourceNames.SECOND, secondDataSource);

        MyRoutingDataSource myRoutingDataSource = new MyRoutingDataSource();
        //                  
        myRoutingDataSource.setDefaultTargetDataSource(firstDataSource);
        myRoutingDataSource.setTargetDataSources(targetDataSources);
        return myRoutingDataSource;
    }
   
}

6、aop注釈により、データソースを動的に切り替える
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 *     ,     
 * @Author lxk
 * @version V1.0.0
 */
@Aspect
@Component
public class DataSourceAspect implements Ordered {

    @Pointcut("@annotation(com.magus.datahub.master.orghandler.config.CurDataSource)")
    public void dataSourcePointCut() {

    }

    @Before("dataSourcePointCut()")
    public void doBefore(JoinPoint  point){
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        CurDataSource ds = method.getAnnotation(CurDataSource.class);
        if (ds == null) {
            DBContextHolder.setDataSource(DataSourceNames.FIRST);
        } else {
            DBContextHolder.setDataSource(ds.name());
        }
    }

    @After("dataSourcePointCut()")
    public void after(JoinPoint point) {
        //           ,           
        DBContextHolder.clearDataSource();
    }

    @Override
    public int getOrder() {
        return -1;
    }
}

7、動的切替データソース、例
コントローラの例
@RequestMapping("/ehr")
public class EhrController {

    @Autowired
    private EhrService ehrService;

    @GetMapping("/test/switch")
    public ResponseEntity> ehrServiceSwitch(){
        return ResponseEntity.ok(ehrService.findAllSwitch());
    }

    @GetMapping("/test")
    public ResponseEntity> ehrService(){
        return ResponseEntity.ok(ehrService.findAll());
    }
}

サービスの例
@Service
public class EhrService {

    @Autowired
    private MasterCompanyRepository companyRepository;

    public List findAll(){
       return companyRepository.findAll();
    }
    
    @CurDataSource(name = DataSourceNames.SECOND)
    public List findAllSwitch(){
        return companyRepository.findAll();
    }
}

トランザクション制御、参照可能https://www.cnblogs.com/jpfss/p/8295692.html