標籤:切換 技術 str nod spec catch ons 顯示 bean
由於公司業務劃分了多個資料庫,開發一個項目會同事調用多個庫,經過學習我們採用了註解+aop的方式實現的
1.首先定義一個註解類
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface TargetDataSource {String value();//此處接收的是資料來源的名稱}
2.然後建一個配置類,這個在項目啟動時會載入資料來源,一開始採用了HikariCP,查資料說是最快效能最好的,然後又發現了阿里的druid,這個功能比較全面,而且效能也還可以,最主要他還有監控功能,具體實現看如下代碼
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@EnableSchedulingpublic class DataSourceConfig {/*@Autowiredprivate 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;}@Beanpublic 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;@Beanpublic 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 */@Beanpublic 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配合使用
public class DynamicDataSource extends AbstractRoutingDataSource{//資料來源路由,此方用於產生要選取的資料來源邏輯名稱@Overrideprotected 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層切換資料來源
@Component@Aspectpublic 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裡面
xml路徑在設定檔裡面配置,這樣適用讀寫分離和多個不同的資料來源,而且多重專案可以共用這一個配置
最後引用註解,需要注意的是註解的資料庫名稱和xml裡面databasename節點是一一對應的,可以隨便自訂,比如讀寫是一個資料庫名字,這時候就可以定義成pringtest_r表示讀庫
至此多資料來源就配置完成,至於阿里的druid下次再分享,代碼都貼出來,如果大家感覺還有哪些不足的地方,歡迎指正。
spring boot+mybatis 多資料來源切換