1. 程式人生 > >Spring boot+Mybatis 使用DruidDataSource 以註解方式動態整合mysql,sqlserver多資料來源

Spring boot+Mybatis 使用DruidDataSource 以註解方式動態整合mysql,sqlserver多資料來源

配置檔案

#多源資料庫配置
datasource:
  mysql:
      type: com.alibaba.druid.pool.DruidDataSource
      driverClassName: com.mysql.jdbc.Driver
      url: jdbc:mysql://x.x.x.x:3306/table?useUnicode=true&useSSL=false&characterEncoding=utf8
      username: root
      password: root
      initialSize: 1
      minIdle:
3 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 30000 validationQuery: select 'x' testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true maxPoolPreparedStatementPerConnectionSize:
20 # 配置監控統計攔截的filters,去掉後監控介面sql無法統計,'wall'用於防火牆 filters: stat,wall,slf4j # 通過connectProperties屬性來開啟mergeSql功能;慢SQL記錄 connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 # 合併多個DruidDataSource的監控資料 #useGlobalDataSourceStat: true sqlserver: url:
jdbc:sqlserver://x.x.x.x:1433;databasename=cwbase001 driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver username: root password: root hrsqlserver: url: jdbc:sqlserver://x.x.x.x:1433;databasename=HR driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver username: root password: root

首先要將spring boot自帶的DataSourceAutoConfiguration禁掉

@SpringBootApplication(exclude = {
        DataSourceAutoConfiguration.class
})
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
        System.out.println("######## 啟動成功 ########");
    }
}

列舉類,多資料來源列舉

public enum DataSourceType {
	Mysql("mysql"),
	SQLServer("sqlserver"),
	HrSQLServer("hrsqlserver");

	private String name;

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

	public String getName() {
		return name;
	}

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

資料庫連線配置實體類,使用者儲存資料相關配置。

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * 資料來源實體類
 */
@Component
@ConfigurationProperties(prefix = "datasource")
public class DataSourceBean {
    
    //mysql 配置
	private Map<String,String> mysql;
    //sql server配置
	private Map<String,String> sqlserver;
    //sql server配置
	private Map<String,String> hrsqlserver;

	public Map<String, String> getMysql() {
		return mysql;
	}

	public void setMysql(Map<String, String> mysql) {
		this.mysql = mysql;
	}

	public Map<String, String> getSqlserver() {
		return sqlserver;
	}

	public void setSqlserver(Map<String, String> sqlserver) {
		this.sqlserver = sqlserver;
	}

	public Map<String, String> getHrsqlserver() {
		return hrsqlserver;
	}

	public void setHrsqlserver(Map<String, String> hrsqlserver) {
		this.hrsqlserver = hrsqlserver;
	}
}

@MyDataSource 註解,在service層中使用,AOP中截獲設定

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface MyDataSource {
	DataSourceType value() default DataSourceType.Mysql;
}

JdbcContextHolder 上下文處理


public class JdbcContextHolder {

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

	public static void putDataSource(String name){
		local.set(name);
	}

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

DynamicDataSource 實現AOP動態切換的關鍵


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

/**
 * AbstractRoutingDataSource實現類DynamicDataSource
 * 實現AOP動態切換的關鍵
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
	@Override
	protected Object determineCurrentLookupKey() {
		String dbName = JdbcContextHolder.getDataSource();
		if (dbName == null ){
			dbName =  DataSourceType.Mysql.getName();
		}
		logger.debug("資料來源為:"+dbName);
		return dbName;
	}
}

資料來源處理 utils

import com.alibaba.druid.pool.DruidDataSource;

import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 資料來源工具
 */
public class DataSourceUtil {

	/**
	 * 獲取指定類的成員變數
	 * @param clazz
	 * @return 成員變數名的List
	 */
	public static List<String> getClassFields(Class clazz){
		List<String> list = new ArrayList<>();
		Field[] fields = clazz.getDeclaredFields();
		for (Field field : fields){
			list.add(field.getName());
		}
		return list;
	}

	/**
	 * 依據成員變數獲取值
	 * @param fieldName 變數名
	 * @param o 已注入的實體
	 * @return Object
	 * @throws Exception 丟擲異常
	 */
	public static Object getFieldValueByName(String fieldName, Object o) throws Exception{
		String firstLetter = fieldName.substring(0, 1).toUpperCase();
		String getter = "get" + firstLetter + fieldName.substring(1);
		Method method = o.getClass().getMethod(getter, new Class[] {});
		Object value = method.invoke(o, new Object[] {});
		return value;
	}

	/**
	 * 依據資料配置 獲取datasource 物件
	 * @param params Map 資料配置
	 * @return 返回datasource
	 * @throws SQLException 丟擲Sql 異常
	 */
	public static DataSource getDataSource(Map<String,String> params) throws SQLException {
		DruidDataSource datasource = new DruidDataSource();
		datasource.setUrl(params.get("url"));
		datasource.setUsername(params.get("username"));
		datasource.setPassword(params.get("password"));
		datasource.setDriverClassName(params.get("driverClassName"));
		if (params.containsKey("initialSize")) {
			datasource.setInitialSize(Integer.parseInt(params.get("initialSize")));
		}
		if (params.containsKey("minIdle")) {
			datasource.setMinIdle(Integer.parseInt(params.get("minIdle")));
		}
		if (params.containsKey("maxActive")) {
			datasource.setMaxActive(Integer.parseInt(params.get("maxActive")));
		}
		if (params.containsKey("maxWait")){
			datasource.setMaxWait(Long.parseLong(params.get("maxWait")));
		}
		if (params.containsKey("timeBetweenEvictionRunsMillis")){
			datasource.setTimeBetweenEvictionRunsMillis(Long.parseLong(params.get("timeBetweenEvictionRunsMillis")));
		}
		if (params.containsKey("minEvictableIdleTimeMillis")){
			datasource.setMinEvictableIdleTimeMillis(Long.parseLong(params.get("minEvictableIdleTimeMillis")));
		}
		if (params.containsKey("validationQuery")){
			datasource.setValidationQuery(params.get("validationQuery"));
		}
		if (params.containsKey("testWhileIdle")){
			datasource.setTestWhileIdle(Boolean.parseBoolean(params.get("testWhileIdle")));
		}
		if (params.containsKey("testOnBorrow")){
			datasource.setTestOnBorrow(Boolean.parseBoolean(params.get("testOnBorrow")));
		}
		if (params.containsKey("testOnReturn")){
			datasource.setTestOnBorrow(Boolean.parseBoolean(params.get("testOnReturn")));
		}
		if (params.containsKey("poolPreparedStatements")){
			datasource.setPoolPreparedStatements(Boolean.parseBoolean(params.get("poolPreparedStatements")));
		}
		if (params.containsKey("maxPoolPreparedStatementPerConnectionSize")){
			datasource.setMaxPoolPreparedStatementPerConnectionSize(
					Integer.parseInt(params.get("maxPoolPreparedStatementPerConnectionSize")));
		}
		if (params.containsKey("filters")){
			datasource.setFilters(params.get("filters"));
		}
		if (params.containsKey("connectionProperties")){
			datasource.setConnectionProperties(params.get("connectionProperties"));
		}
		return datasource;
	}
}

資料來源配置,整合Druid

import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

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

/**
 * 資料庫配置
 */
@SuppressWarnings("AlibabaRemoveCommentedCode")
@Configuration
public class DataSourceConfig {

	private Logger logger = LoggerFactory.getLogger(this.getClass());

	@Autowired
	private DataSourceBean dataSourceBean;

	@Bean(name = "dynamicDataSource")
	@Primary  //優先使用,多資料來源
	public DataSource dataSource(){
		DynamicDataSource dynamicDataSource = new DynamicDataSource();
		//配置多個數據源
		Map<Object,Object> map = new HashMap<>();

		List<String> fields = DataSourceUtil.getClassFields(DataSourceBean.class);
		int i = 0;
		for (String field:fields){
			Map<String,String> config = null;
			try {
				config = (Map<String, String>) DataSourceUtil.getFieldValueByName(field,dataSourceBean);
			} catch (Exception e) {
				e.printStackTrace();
			}
			if (config == null){
				logger.error("資料來源配置失敗:"+field);
				continue;
			}
			try {
				DataSource dataSource = DataSourceUtil.getDataSource(config);
				if (i == 0){
					logger.debug("設定預設資料來源:"+field);
					dynamicDataSource.setDefaultTargetDataSource(dataSource);
				}
				map.put(field,DataSourceUtil.getDataSource(config));
				logger.debug("連結資料庫:"+field);
				i++;
			} catch (SQLException e) {
				logger.error("druid configuration initialization filter", e);
			}
		}
		logger.debug("共配置了"+i+"個數據源");
		dynamicDataSource.setTargetDataSources(map);
		return dynamicDataSource;
	}

	@Bean(name="druidServlet")
	public ServletRegistrationBean druidServlet() {
		ServletRegistrationBean reg = new ServletRegistrationBean();
		reg.setServlet(new StatViewServlet());
		reg.addUrlMappings("/druid/*");
		reg.addInitParameter("allow", ""); //白名單
		return reg;
	}

	@Bean(name = "filterRegistrationBean")
	public FilterRegistrationBean filterRegistrationBean() {
		FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
		filterRegistrationBean.setFilter(new WebStatFilter());
		filterRegistrationBean.addUrlPatterns("/*");
		filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
		filterRegistrationBean.addInitParameter("profileEnable", "true");
		filterRegistrationBean.addInitParameter("principalCookieName","USER_COOKIE");
		filterRegistrationBean.addInitParameter("principalSessionName","USER_SESSION");
		filterRegistrationBean.addInitParameter("DruidWebStatFilter","/*");
		return filterRegistrationBean;
	}
}

DataSourceAspect Aop依據上下文進行賦值

import org.aspectj.lang.JoinPoint;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.xml.crypto.Data;
import java.lang.reflect.Method;

/**
 * AOP根據註解給上下文賦值
 */
@Aspect
@Order(3)
@Component
public class DataSourceAspect {
	private Logger logger = LoggerFactory.getLogger(this.getClass());

	//切點
	@Pointcut("execution(* com.xxx.*.service..*(..)))")
	public void aspect(){
		System.out.println("aspect");
	}

	@Before("aspect()")
	private void before(JoinPoint joinPoint){
		Object target = joinPoint.getTarget();
		String method = joinPoint.getSignature().getName();
		Class<?> classz = target.getClass();
		Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameterTypes();
		try {
			Method m = classz.getMethod(method,parameterTypes);
			if (m != null && m.isAnnotationPresent(MyDataSource.class)){
				MyDataSource data = m.getAnnotation(MyDataSource.class);
				JdbcContextHolder.putDataSource(data.value().getName());
				logger.debug("===============上下文賦值完成:"+data.value().getName());
			}else{
				JdbcContextHolder.putDataSource(DataSourceType.Mysql.getName());
				logger.debug("===============使用預設資料來源:"+DataSourceType.Mysql.getName());
			}
		}catch (Exception e){
			e.printStackTrace();
		}
	}
}

目錄結構


使用方式 在service方法上寫入註解

	@Override
	@MyDataSource(DataSourceType.Mysql)
	//@MyDataSource(DataSourceType.HrSQLServer)
    //#@MyDataSource(DataSourceType.SQLServer)
	public int count(HelpDO helpDO, Map<String, Object> map) {
		return helpDao.count(getQuery(helpDO,map));
	}