spring boot+mybatis 多數據源切換

因爲公司業務劃分了多個數據庫,開發一個項目會同時調用多個庫,通過學習咱們採用了註解+aop的方式實現的css

1.首先定義一個註解類java

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TargetDataSource {
	String value();//此處接收的是數據源的名稱
}

2.而後建一個配置類,這個在項目啓動時會加載數據源,一開始採用了HikariCP,查資料說是最快性能最好的,而後又發現了阿里的druid,這個功能比較全面,並且性能也還能夠,最主要他還有監控功能,具體實現看以下代碼node

 

package com.example.demo.datasource;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import com.example.demo.datasource.DynamicDataSource;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
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.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.PlatformTransactionManager;
import org.w3c.dom.NodeList;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.sql.DataSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.io.File;
import com.alibaba.druid.support.http.StatViewServlet;
/**
 * Author:   wangchao
 * Version:
 * Date:     2017/9/11
 * Description:數據源配置
 * Modification  History:
 * Date         	Author        		Version        	Description
 * --------------------------------------------------------------
 * Why & What is modified:
 */

@Configuration
@EnableScheduling
public class DataSourceConfig {

	/*@Autowired
	private DBProperties properties;*/
	@Value("${datasource.filePath}")
	private String filePath;//數據源配置

	@Bean(name = "dataSource")
	public DataSource dataSource() {
		//按照目標數據源名稱和目標數據源對象的映射存放在Map中
		Map<Object, Object> targetDataSources = new HashMap<>();
		//查找xml數據鏈接字符串
		targetDataSources=getdataMap(filePath);
		//動態獲取DBProperties類申明的屬性
		/*Field[] fields=properties.getClass().getDeclaredFields();
		for(int i=0;i<fields.length;i++)
		{
			targetDataSources.put(fields[i].getName(), getFieldValueByName(fields[i].getName(),properties));
		}*/
		//採用是想AbstractRoutingDataSource的對象包裝多數據源
		DynamicDataSource dataSource = new DynamicDataSource();
		dataSource.setTargetDataSources(targetDataSources);
		//設置默認的數據源,當拿不到數據源時,使用此配置
		//dataSource.setDefaultTargetDataSource(properties.getUzaiTravel());
		return dataSource;
	}

	@Bean
	public PlatformTransactionManager txManager() {
		return new DataSourceTransactionManager(dataSource());
	}

	/**
	*獲取數據源集合
	*/

	private Map<Object, Object> getdataMap(String fiePath)
	{

		try {
			Map<Object, Object> targetDataSources = new HashMap<>();
			File xmlFile = new File(fiePath);

			DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();

			DocumentBuilder builder = builderFactory.newDocumentBuilder();

			Document doc = builder.parse(xmlFile);

			doc.getDocumentElement().normalize();

			System.out.println("Root element: " + doc.getDocumentElement().getNodeName());

			NodeList nList = doc.getElementsByTagName("db");
			for(int i = 0 ; i<nList.getLength();i++) {

				Node node = nList.item(i);
				Element ele = (Element)node;

				/*HikariConfig config = new HikariConfig();
				config.setDriverClassName(ele.getElementsByTagName("driver-class").item(0).getTextContent());
				config.setJdbcUrl(ele.getElementsByTagName("jdbc-url").item(0).getTextContent());
				config.setUsername(ele.getElementsByTagName("username").item(0).getTextContent());
				config.setPassword(ele.getElementsByTagName("password").item(0).getTextContent());
				//config.addDataSourceProperty("password", ele.getElementsByTagName("password").item(0).getTextContent());
				HikariDataSource dataSource = new HikariDataSource(config);*/


				DruidDataSource dataSource = new DruidDataSource();
				dataSource.setDriverClassName(ele.getElementsByTagName("driver-class").item(0).getTextContent());
				dataSource.setUsername(ele.getElementsByTagName("username").item(0).getTextContent());
				dataSource.setPassword(ele.getElementsByTagName("password").item(0).getTextContent());
				dataSource.setUrl(ele.getElementsByTagName("jdbc-url").item(0).getTextContent());
				dataSource.setInitialSize(5);
				dataSource.setMinIdle(1);
				dataSource.setMaxActive(10);// 啓用監控統計功能
				dataSource.setFilters("stat");//設置是否顯示sql語句
				targetDataSources.put(ele.getElementsByTagName("databasename").item(0).getTextContent(), dataSource);
			}
			return targetDataSources;
		}
		catch (Exception ex)
		{
			return null;
		}

	}
    //訪問的ip
	@Value("${druid.IP}")
	private String IP;
    //登陸名
	@Value("${druid.druidLgoinName}")
	private String druidLgoinName;
    //密碼
	@Value("${druid.druidLgoinPassword}")
	private String druidLgoinPassword;

	@Bean
	public ServletRegistrationBean DruidStatViewServle() {
		//org.springframework.boot.context.embedded.ServletRegistrationBean提供類的進行註冊.
		ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
		//添加初始化參數:initParams

		//白名單:
		servletRegistrationBean.addInitParameter("allow",IP);
		//IP黑名單 (存在共同時,deny優先於allow) : 若是知足deny的話提示:Sorry, you are not permitted to view this page.
		// servletRegistrationBean.addInitParameter("deny", "192.168.1.73");
		//登陸查看信息的帳號密碼.
		servletRegistrationBean.addInitParameter("loginUsername",druidLgoinName);
		servletRegistrationBean.addInitParameter("loginPassword",druidLgoinPassword);
		//是否可以重置數據.
		servletRegistrationBean.addInitParameter("resetEnable","false");
		return servletRegistrationBean;
	}

	/**

	 * 註冊一個:filterRegistrationBean

	 * @return

    */
	@Bean
	public FilterRegistrationBean druidStatFilter2(){
		FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
		//添加過濾規則.
		filterRegistrationBean.addUrlPatterns("/*");
		//添加不須要忽略的格式信息.
		filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
		return filterRegistrationBean;
	}

}

3.動態數據源,從以前已加載的數據源中選取,DynamicDataSource和DynamicDataSourceHolder配合使用web

public class DynamicDataSource extends AbstractRoutingDataSource{
	//數據源路由,此方用於產生要選取的數據源邏輯名稱
	@Override
	protected Object determineCurrentLookupKey() {
		//從共享線程中獲取數據源名稱
		return DynamicDataSourceHolder.getDataSource();
	}
}
public class DynamicDataSourceHolder {
	/**
	 * 本地線程共享對象
	 */
	private static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();

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

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

	public static void removeDataSource() {
		THREAD_LOCAL.remove();
	}
}

5.就是使用aop,在dao層切換數據源spring

@Component
@Aspect
public class DataSourceAspect {
	//切換放在mapper接口的方法上,因此這裏要配置AOP切面的切入點
	@Pointcut("execution( * com.example.demo.dao.*.*(..))")
	public void dataSourcePointCut() {
	}

	@Before("dataSourcePointCut()")
	public void before(JoinPoint joinPoint) {
		Object target = joinPoint.getTarget();
		String method = joinPoint.getSignature().getName();
		Class<?>[] clazz = target.getClass().getInterfaces();
		Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameterTypes();
		try {
			Method m = clazz[0].getMethod(method, parameterTypes);
			//若是方法上存在切換數據源的註解,則根據註解內容進行數據源切換
			if (m != null && m.isAnnotationPresent(TargetDataSource.class)) {
				TargetDataSource data = m.getAnnotation(TargetDataSource.class);
				String dataSourceName = data.value();
				DynamicDataSourceHolder.putDataSource(dataSourceName);

			} else {

			}
		} catch (Exception e) {

		}
	}

	//執行完切面後,將線程共享中的數據源名稱清空
	@After("dataSourcePointCut()")
	public void after(JoinPoint joinPoint){
		DynamicDataSourceHolder.removeDataSource();
	}
}

  

 數據鏈接都配置在xml裏面sql

 xml路徑在配置文件裏面配置,這樣適用讀寫分離和多個不一樣的數據源,並且多個項目能夠共用這一個配置數據庫

 

 最後引用註解,須要注意的是註解的數據庫名稱和xml裏面databasename節點是一一對應的,能夠隨便自定義,好比讀寫是一個數據庫名字,這時候就能夠定義成pringtest_r表示讀庫mybatis

 

 

 至此多數據源就配置完成,至於阿里的druid下次再分享,代碼都貼出來,若是你們感受還有哪些不足的地方,歡迎指正。app

相關文章
相關標籤/搜索