SpringBoot多數據源配置-實戰

一、前言

這次項目涉及到模板配置,dba建議我們將配置的表單獨放到配置庫裏面,所以這裏我們需要在項目裏面配置多數據源,然後不同的服務調用不同的數據源。數據庫的密碼比較敏感,我們是配置在百度的Disconf裏面,遠程獲取。
在這裏插入圖片描述


二、配置文件

由於我們使用了disconf,數據源配置寫在*.yml文件裏面,無法獲取disconf配置,最後配置在*.xml文件裏面:

 <!--數據源1-->
    <bean id="dataSource1" class="com.alibaba.druid.pool.DruidDataSource" lazy-init="false"
          init-method="init" destroy-method="close">
        <property name="driverClassName">
            <value>${cxx.jdbc.driver}</value>
        </property>
        <property name="url">
            <value>${cxx.jdbc.url}</value>
        </property>
        <property name="username">
            <value>${cxx.jdbc.username}</value>
        </property>
        <property name="password">
            <value>${cxx.jdbc.password}</value>
        </property>
        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="10"/>
        <property name="minIdle" value="10"/>
        <property name="maxActive" value="50"/>

        <!-- 配置獲取連接等待超時的時間 -->
        <property name="maxWait" value="5000"/>

        <!-- 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連接,單位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="60000"/>

        <!-- 配置一個連接在池中最小生存的時間,單位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="300000"/>

        <property name="validationQuery" value="SELECT 1"/>
        <property name="testWhileIdle" value="true"/>
        <property name="testOnBorrow" value="false"/>
        <property name="testOnReturn" value="false"/>

        <!-- 打開PSCache,並且指定每個連接上PSCache的大小 -->
        <property name="poolPreparedStatements" value="false"/>
        <!--<property name="maxPoolPreparedStatementPerConnectionSize" value="50"/> -->
        <property name="connectionInitSqls" value="set names utf8mb4"/>
    </bean>

    <!--數據源2-->
    <bean id="dataSource2" class="com.alibaba.druid.pool.DruidDataSource" lazy-init="false"
          init-method="init" destroy-method="close">
        <property name="driverClassName">
            <value>${cxx.jdbc.driver}</value>
        </property>
        <property name="url">
            <value>${cxx.config.jdbc.url}</value>
        </property>
        <property name="username">
            <value>${cxx.config.jdbc.username}</value>
        </property>
        <property name="password">
            <value>${cxx.config.jdbc.password}</value>
        </property>
        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="10"/>
        <property name="minIdle" value="10"/>
        <property name="maxActive" value="50"/>

        <!-- 配置獲取連接等待超時的時間 -->
        <property name="maxWait" value="5000"/>

        <!-- 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連接,單位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="60000"/>

        <!-- 配置一個連接在池中最小生存的時間,單位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="300000"/>

        <property name="validationQuery" value="SELECT 1"/>
        <property name="testWhileIdle" value="true"/>
        <property name="testOnBorrow" value="false"/>
        <property name="testOnReturn" value="false"/>

        <!-- 打開PSCache,並且指定每個連接上PSCache的大小 -->
        <property name="poolPreparedStatements" value="false"/>
        <!--<property name="maxPoolPreparedStatementPerConnectionSize" value="50"/> -->
        <property name="connectionInitSqls" value="set names utf8mb4"/>
    </bean>

三、DataSourceConfig

package com.cxx.datasources;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;

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

/**
 * User: lanxinghua
 * Date: 2019/6/17 12:07
 * Desc: 配置多數據源
 */
@Configuration
public class DynamicDataSourceConfig {
    //通過讀取yml配置文件,創建數據源
    @Bean("dataSource1")
    @ConfigurationProperties("spring.datasource.druid.mis")
    public DataSource misDataSource(){
        return DruidDataSourceBuilder.create().build();
    }
    @Bean("dataSource2")
    @ConfigurationProperties("spring.datasource.druid.misconfig")
    public DataSource misConfigDataSource(){
        return DruidDataSourceBuilder.create().build();
    }

    @Bean("dynamicDataSource")
    //解決互相依賴關係
    @DependsOn({ "dataSource1", "dataSource2"})
    @Primary
    public DynamicDataSource getDataSoruce(DataSource dataSource1, DataSource dataSource2) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceNames.D1, dataSource1);
        targetDataSources.put(DataSourceNames.D2, dataSource2);
        //默認創建misDataSource數據源
        return new DynamicDataSource(dataSource1, targetDataSources);
    }
}
package com.cxx.datasources;

/**
 * User: lanxinghua
 * Date: 2019/6/17 12:04
 * Desc: 增加多數據源
 */
public interface DataSourceNames {
    String MIS = "D1";
    String MISCONFIG = "D2";
}

四、通過繼承AbstractRoutingDataSource實現其動態選擇數據源

package com.cxx.datasources;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.Map;

/**
 * User: lanxinghua
 * Date: 2019/6/17 12:05
 * Desc: 動態數據源
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    //設置本地線程全局變量,保證線程安全
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }

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

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

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

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

五、自定義註解

package com.cxx.datasources.annotation;

import java.lang.annotation.*;
/**
 * User: lanxinghua
 * Date: 2019/6/17 12:03
 * Desc: 多數據源註解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    String name() default "";
}

六、AOP進行動態數據源操作

package com.cxx.datasources.aspect;

import com.cxx.log.Logger;
import com.cxx.log.LoggerFactory;
import com.cxx.datasources.DataSourceNames;
import com.cxx.datasources.DynamicDataSource;
import com.cxx.datasources.annotation.DataSource;
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.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;

/**
 * User: lanxinghua
 * Date: 2019/6/17 12:13
 * Desc: 多數據源,切面處理類
 */
@Aspect
@Component
public class DataSourceAspect implements Ordered {
    private static final Logger logger = LoggerFactory.getLogger(DataSourceAspect.class);
    //定義切面
    @Pointcut("@annotation(com.cxx.datasources.annotation.DataSource)")
    public void dataSourcePointCut() {
    }
    //環繞通知
    @Around("dataSourcePointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        DataSource ds = method.getAnnotation(DataSource.class);
        if(ds == null){
            DynamicDataSource.setDataSource(DataSourceNames.MIS);
            logger.debug("set datasource is " + DataSourceNames.MIS);
        }else {
            DynamicDataSource.setDataSource(ds.name());
            logger.debug("set datasource is " + ds.name());
        }
        try {
            return point.proceed();
        } finally {
            DynamicDataSource.clearDataSource();
            logger.debug("clean datasource");
        }
    }
    //order切面的優先級,值越小優先級越高
    @Override
    public int getOrder() {
        return 1;
    }
}

七、測試

package com.cxx;
import com.alibaba.fastjson.JSON;
import com.cxx.client.contractnew.vo.ContractVo;
import com.cxx.manager.impl.KeyTypeManager;
import junit.framework.TestCase;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = MisApplication.class)// 指定spring-boot的啓動類
public class BaseTest extends TestCase {
    @Resource
    private KeyTypeManager keyTypeManager;

    //多數據源測試
    //在需要配置數據源的方法上加上註解 @DataSource(name = DataSourceNames.MISCONFIG)
    @Test
    public void ContractTypeConfigTest() {
        ContractVo  vo = keyTypeManager.keyList(1, 3);
        System.out.println(JSON.toJSONString(vo));
    }
}


總結

我們擴展一下,怎麼來通過配置數據源,實現數據庫讀寫分離???

其實想一下也很簡單,那就是讀用一個數據源,寫用一個數據源。那代碼層面在AOP裏面切面處理一下就ok。

@Aspect
@Component
@Order(-1)
@Slf4j
public class DataSourcePartAspect {
    /**
     * mapper 查詢操作默認使用從庫
     */
    @Before("execution(* com..service..*.select*(..)) || execution(* com..service..*.get*(..))|| execution(* com..service..*.query*(..))")
    public void setReadDataSourceType() {
        DynamicDataSourceHolder.putDataSource(DataSourceType.SLAVE);
        log.info("dataSource 切換到:{}",DataSourceType.SLAVE.getName());
    }
 
    /**
     * mapper 修改刪除操作默認使用主庫庫
     */
    @Before("execution(* com..service..*.insert*(..)) || execution(* com..service..*.update*(..)) || execution(* com..service..*.delete*(..))")
    public void setWriteDataSourceType() {
        DynamicDataSourceHolder.putDataSource(DataSourceType.MASTER);
        log.info("dataSource 切換到:{}",DataSourceType.MASTER.getName());
    }
 
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章