一、前言
這次項目涉及到模板配置,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());
}
}