springboot項目自定義註解實現的多數據源切換

1、主要依賴

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.4.RELEASE</version>
    <relativePath/>
</parent>
    
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    <version>2.1.4.RELEASE</version>
</dependency>
dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.10</version>
</dependency>

2、yml

# 數據源配置
spring:
    datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.jdbc.Driver
        druid:
            # 主庫數據源
            master:
                url: jdbc:mysql://127.0.0.1:3306/master?characterEncoding=UTF-8
                username: root
                password: root
            #樹熊數據源
            slave:
                enabled : true
                url: jdbc:mysql:////127.0.0.1:3306/slave?characterEncoding=UTF-8
                username: root
                password: root
            

            # 初始鏈接數
            initial-size: 10
            # 最大鏈接池數量
            max-active: 100
            # 最小鏈接池數量
            min-idle: 10
            # 配置獲取鏈接等待超時的時間
            max-wait: 60000
            # 打開PSCache,而且指定每一個鏈接上PSCache的大小
            pool-prepared-statements: true
            max-pool-prepared-statement-per-connection-size: 20
            # 配置間隔多久才進行一次檢測,檢測須要關閉的空閒鏈接,單位是毫秒
            timeBetweenEvictionRunsMillis: 60000
            # 配置一個鏈接在池中最小生存的時間,單位是毫秒
            min-evictable-idle-time-millis: 300000
            validation-query: SELECT 1 FROM DUAL
            test-while-idle: true
            test-on-borrow: false
            test-on-return: false
            stat-view-servlet:
                enabled: true
                url-pattern: /druid/*
            filter:
                stat:
                    log-slow-sql: true
                    slow-sql-millis: 1000
                    merge-sql: false
                wall:
                    config:
                        multi-statement-allow: true

3、實現

3.一、@DataSource和DataSourceType

/**
 * 數據源
 * @author DUCHONG
 */
public enum DataSourceType
{
    /**
     * 主庫
     */
    MASTER,

    /**
     * 從庫
     */
    SLAVE
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定義多數據源切換註解
 *
 * @author DUCHONG
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource
{
    /**
     * 切換數據源名稱
     */
    public DataSourceType value() default DataSourceType.MASTER;
}

3.二、DynamicDataSourceContextHolder

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 數據源切換處理
 *
 * @author DUCHONG
 */
public class DynamicDataSourceContextHolder
{
    public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);

    /**
     * 使用ThreadLocal維護變量,ThreadLocal爲每一個使用該變量的線程提供獨立的變量副本,
     *  因此每個線程均可以獨立地改變本身的副本,而不會影響其它線程所對應的副本。
     */
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    /**
     * 設置數據源的變量
     */
    public static void setDateSourceType(String dsType)
    {
        log.info("切換到{}數據源", dsType);
        CONTEXT_HOLDER.set(dsType);
    }

    /**
     * 得到數據源的變量
     */
    public static String getDateSourceType()
    {
        return CONTEXT_HOLDER.get();
    }

    /**
     * 清空數據源變量
     */
    public static void clearDateSourceType()
    {
        CONTEXT_HOLDER.remove();
    }
}

3.三、繼承AbstractRoutingDataSource

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

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

/**
 * 動態數據源
 *
 * @author DUCHONG
 */
public class DynamicDataSource extends AbstractRoutingDataSource
{
    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources)
    {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }

    @Override
    protected Object determineCurrentLookupKey()
    {
        return DynamicDataSourceContextHolder.getDateSourceType();
    }
}

3.四、定義切面

import com.starfast.admin.common.annotation.DataSource;
import com.starfast.admin.datasource.DynamicDataSourceContextHolder;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 多數據源處理
 * @author DUCHONG
 */
@Aspect
@Order(1)
@Component
public class DataSourceAspect
{
    protected Logger logger = LoggerFactory.getLogger(getClass());

    @Pointcut("@annotation(com.duchong.common.annotation.DataSource)")
    public void dsPointCut()
    {

    }

    @Around("dsPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable
    {
        MethodSignature signature = (MethodSignature) point.getSignature();

        Method method = signature.getMethod();

        DataSource dataSource = method.getAnnotation(DataSource.class);

        if (null!=dataSource)
        {
            DynamicDataSourceContextHolder.setDateSourceType(dataSource.value().name());
        }

        try
        {
            return point.proceed();
        }
        finally
        {
            // 銷燬數據源 在執行方法以後
            DynamicDataSourceContextHolder.clearDateSourceType();
        }
    }
}

3.五、@Configuration

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.starfast.admin.common.enums.DataSourceType;
import com.starfast.admin.datasource.DynamicDataSource;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

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

/**
 * druid 配置多數據源
 *
 * @author DUCHONG
 */
@Configuration
public class DruidConfig
{
    @Bean
    @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource masterDataSource()
    {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.druid.slave")
    @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
    public DataSource slaveDataSource()
    {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean(name = "dynamicDataSource")
    @Primary
    public DynamicDataSource dataSource()
    {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource());
        targetDataSources.put(DataSourceType.TREEBEAR.name(), slaveDataSource());
        return new DynamicDataSource(masterDataSource(), targetDataSources);
    }
}

3.六、使用

須要切換數據源的方法上加java

@DataSource(value = DataSourceType.SLAVE)

 

結束。mysql

相關文章
相關標籤/搜索