天天看點

基于Spring Boot 動态主從資料庫路由

本文簡單的介紹一下基于Spring boot架構動态連接配接多資料源的實作,在配置檔案中添加多個資料源,采用主從配置的方式,配置zjsz、cloud兩個資料庫。

項目的目錄結構:

基于Spring Boot 動态主從資料庫路由

application.properties

基于Spring Boot 動态主從資料庫路由

核心主要是dynamic和aop目錄

1.首先配置資料源資訊,在spring5.x之後開始預設使用HikariCP來做資料源

@Configuration
public class DataSourceConfig {

    @Bean(name = "masterDataSource")
    @ConfigurationProperties(prefix = "spirng.master.datasource")
    public HikariDataSource masterDataSource() {
        return new HikariDataSource();
    }


    @Bean(name = "slaveDataSource")
    @ConfigurationProperties(prefix = "spirng.slave.datasource")
    public HikariDataSource slaveDataSource() {
        return new HikariDataSource();
    }

    //動态資料源
    @Bean(name = "dynamicDataSource")
    //解決互相依賴關系
    @DependsOn({ "masterDataSource", "slaveDataSource"})
    @Primary
    public DataSource getDataSource() {
        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(targetDataSources());
        return dataSource;
    }

    private Map<Object, Object> targetDataSources() {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceType.MASTER.getType(), masterDataSource());
        targetDataSources.put(DataSourceType.SLAVE.getType(), slaveDataSource());
        return targetDataSources;
    }
}
           
public enum DataSourceType {
    SLAVE("slave", "從庫"),
    MASTER("master", "主庫");

    private String type;
    private String name;

    DataSourceType(String type, String name) {
        this.type = type;
        this.name = name;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
           

2.自定義Datasource注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
@Inherited
public @interface DataSource {

    /**
     * 資料庫路由
     */
    DataSourceType value() default DataSourceType.MASTER;
}
           

3.使用ThreadLocal來保證從@DataSource擷取的路由key是線程安全的

public class DynamicDataSourceHolder {

    private static final ThreadLocal<String> holder = new ThreadLocal<>();

    public static void putDataSource(DataSourceType dataSourceType) {
        holder.set(dataSourceType.getType());
    }

    public static String getDataSouce() {
        return holder.get();
    }
}
           

4.繼承AbstractRoutingDataSource并重寫determineCurrentLookupKey法法

使用AbstractRoutingDataSource 的實作類,進行靈活的切換,可以通過AOP或者手動程式設計設定目前的DataSource,這樣的編寫方式比較好。

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        if (DynamicDataSourceHolder.getDataSouce() != null) {
            return DynamicDataSourceHolder.getDataSouce();
        }
        return DataSourceType.MASTER.getType();
    }
}
           

5.使用AOP進行攔截帶有@DataSource注解的方法,并擷取注解的值,進行動态資料源操作

@Aspect
@Component
public class DataSourceAspect implements Ordered {

//    @Pointcut("execution(public * com.aop.jdbc.impl..*.*(..))")
//    public void dynamic() {
//
//    }


    @Before(value = "@annotation(dataSource)")
    public void dataSourcePoint(JoinPoint joinPoint, DataSource dataSource) {
        DynamicDataSourceHolder.putDataSource(dataSource.value());
    }

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

6.使用方法

@Service("testService")
public class TestServiceImpl implements ITestService {

    @Autowired
    private OwnCodeMapper ownCodeMapper;

    @DataSource(DataSourceType.MASTER)
    @Override
    public String save() {
        System.out.println("=======save======進入主庫操作========save======");
        System.out.println("删除影響行數:" + this.ownCodeMapper.deleteByPrimaryKey(2));
        return null;
    }

    @DataSource(DataSourceType.SLAVE)
    @Override
    public String query() {
        System.out.println("=======query======進入從庫操作=========query=====");
        System.out.println("查詢結果:" + this.ownCodeMapper.selectByPrimaryKey(36));
        return null;
    }
}
           

到此就完成了動态主從路由的代碼開發。

源碼位址,大家幫忙star哦!!