SSM框架之多資料來源配置
阿新 • • 發佈:2018-12-15
多資料來源的應用場景:主要是資料庫拆分後,怎樣讓多個數據庫結合起來來達到業務需求。
SSM框架(Spring+SpringMVC+MyBatis(MyBatis-Plus))是目前最常用的,此次仍然是maven工程。
關於這個多資料來源例子,我已經上傳到我的github上,地址為:https://github.com/youcong1996/study_simple_demo.git
不過需要注意的是,分支為demo1,不是主分支,如圖所示:
如果下面的示例,你們看不懂或者不能理解,可以git clone我的地址
在程式設計的世界裡,簡潔即完美。
如何實現多資料來源?
一句話,三個類加xml配置即可達到這個目的。
一、編寫三個類
AbstractDynamicDataSource.java
package com.blog.datasource; import java.util.Map; import javax.sql.DataSource; import org.apache.commons.collections.MapUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * 動態資料來源父類 * @create ll * @update * @updateDate */ public abstract class AbstractDynamicDataSource<T extends DataSource> extends AbstractRoutingDataSourceimplements ApplicationContextAware { /** 日誌 */ protected Logger logger = LoggerFactory.getLogger(getClass()); /** 預設的資料來源KEY */ protected static final String DEFAULT_DATASOURCE_KEY = "defaultDataSource"; /** 資料來源KEY-VALUE鍵值對 */ public Map<Object, Object> targetDataSources; /** spring容器上下文 */ private static ApplicationContext ctx; public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { ctx = applicationContext; } public static ApplicationContext getApplicationContext() { return ctx; } public static Object getBean(String name) { return ctx.getBean(name); } /** * @param targetDataSources the targetDataSources to set */ public void setTargetDataSources(Map<Object, Object> targetDataSources) { this.targetDataSources = targetDataSources; super.setTargetDataSources(targetDataSources); // afterPropertiesSet()方法呼叫時用來將targetDataSources的屬性寫入resolvedDataSources中的 super.afterPropertiesSet(); } /** * 建立資料來源 * @param driverClassName 資料庫驅動名稱 * @param url 連線地址 * @param username 使用者名稱 * @param password 密碼 * @return 資料來源{@link T} * @Author : ll. create at 2017年3月27日 下午2:44:34 */ public abstract T createDataSource(String driverClassName, String url, String username, String password); /** * 設定系統當前使用的資料來源 * <p>資料來源為空或者為0時,自動切換至預設資料來源,即在配置檔案中定義的預設資料來源 * @see org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource#determineCurrentLookupKey() */ @Override protected Object determineCurrentLookupKey() { logger.info("【設定系統當前使用的資料來源】"); Map<String, Object> configMap = DBContextHolder.getDBType(); logger.info("【當前資料來源配置為:{}】", configMap); if (MapUtils.isEmpty(configMap)) { // 使用預設資料來源 return DEFAULT_DATASOURCE_KEY; } // 判斷資料來源是否需要初始化 this.verifyAndInitDataSource(); logger.info("【切換至資料來源:{}】", configMap); return configMap.get(DBContextHolder.DATASOURCE_KEY); } /** * 判斷資料來源是否需要初始化 * @Author : ll. create at 2017年3月27日 下午3:57:43 */ private void verifyAndInitDataSource() { Map<String, Object> configMap = DBContextHolder.getDBType(); Object obj = this.targetDataSources.get(configMap.get(DBContextHolder.DATASOURCE_KEY)); if (obj != null) { return; } logger.info("【初始化資料來源】"); T datasource = this.createDataSource(configMap.get(DBContextHolder.DATASOURCE_DRIVER) .toString(), configMap.get(DBContextHolder.DATASOURCE_URL).toString(), configMap.get(DBContextHolder.DATASOURCE_USERNAME).toString(), configMap.get(DBContextHolder.DATASOURCE_PASSWORD).toString()); this.addTargetDataSource(configMap.get(DBContextHolder.DATASOURCE_KEY).toString(), datasource); } /** * 往資料來源key-value鍵值對集合新增新的資料來源 * @param key 新的資料來源鍵 * @param dataSource 新的資料來源 */ private void addTargetDataSource(String key, T dataSource) { this.targetDataSources.put(key, dataSource); super.setTargetDataSources(this.targetDataSources); // afterPropertiesSet()方法呼叫時用來將targetDataSources的屬性寫入resolvedDataSources中的 super.afterPropertiesSet(); } }
DBContextHolder.java
package com.blog.datasource; import java.util.HashMap; import java.util.Map; public class DBContextHolder { /** 資料來源的KEY */ public static final String DATASOURCE_KEY = "DATASOURCE_KEY"; /** 資料來源的URL */ public static final String DATASOURCE_URL = "DATASOURCE_URL"; /** 資料來源的驅動 */ public static final String DATASOURCE_DRIVER = "DATASOURCE_DRIVER"; /** 資料來源的使用者名稱 */ public static final String DATASOURCE_USERNAME = "DATASOURCE_USERNAME"; /** 資料來源的密碼 */ public static final String DATASOURCE_PASSWORD = "DATASOURCE_PASSWORD"; private static final ThreadLocal<Map<String, Object>> contextHolder = new ThreadLocal<Map<String, Object>>(); public static void setDBType(Map<String, Object> dataSourceConfigMap) { contextHolder.set(dataSourceConfigMap); } public static Map<String, Object> getDBType() { Map<String, Object> dataSourceConfigMap = contextHolder.get(); if (dataSourceConfigMap == null) { dataSourceConfigMap = new HashMap<String, Object>(); } return dataSourceConfigMap; } public static void clearDBType() { contextHolder.remove(); } }
DruidDynamicDataSource.java
package com.blog.datasource; import java.sql.SQLException; import java.util.List; import org.apache.commons.lang3.StringUtils; import com.alibaba.druid.filter.Filter; import com.alibaba.druid.pool.DruidDataSource; /** * Druid資料來源 * @update * @updateDate */ public class DruidDynamicDataSource extends AbstractDynamicDataSource<DruidDataSource> { private boolean testWhileIdle = true; private boolean testOnBorrow = false; private boolean testOnReturn = false; // 是否開啟連線洩露自動檢測 private boolean removeAbandoned = false; // 連線長時間沒有使用,被認為發生洩露時長 private long removeAbandonedTimeoutMillis = 300 * 1000; // 發生洩露時是否需要輸出 log,建議在開啟連線洩露檢測時開啟,方便排錯 private boolean logAbandoned = false; // 只要maxPoolPreparedStatementPerConnectionSize>0,poolPreparedStatements就會被自動設定為true,使用oracle時可以設定此值。 // private int maxPoolPreparedStatementPerConnectionSize = -1; // 配置監控統計攔截的filters private String filters; // 監控統計:"stat" 防SQL注入:"wall" 組合使用: "stat,wall" private List<Filter> filterList; /* * 建立資料來源 * @see com.cdelabcare.pubservice.datasource.IDynamicDataSource#createDataSource(java.lang.String, java.lang.String, java.lang.String, java.lang.String) */ @Override public DruidDataSource createDataSource(String driverClassName, String url, String username, String password) { DruidDataSource parent = (DruidDataSource) super.getApplicationContext().getBean( DEFAULT_DATASOURCE_KEY); DruidDataSource ds = new DruidDataSource(); ds.setUrl(url); ds.setUsername(username); ds.setPassword(password); ds.setDriverClassName(driverClassName); ds.setInitialSize(parent.getInitialSize()); ds.setMinIdle(parent.getMinIdle()); ds.setMaxActive(parent.getMaxActive()); ds.setMaxWait(parent.getMaxWait()); ds.setTimeBetweenConnectErrorMillis(parent.getTimeBetweenConnectErrorMillis()); ds.setTimeBetweenEvictionRunsMillis(parent.getTimeBetweenEvictionRunsMillis()); ds.setMinEvictableIdleTimeMillis(parent.getMinEvictableIdleTimeMillis()); ds.setValidationQuery(parent.getValidationQuery()); ds.setTestWhileIdle(testWhileIdle); ds.setTestOnBorrow(testOnBorrow); ds.setTestOnReturn(testOnReturn); ds.setRemoveAbandoned(removeAbandoned); ds.setRemoveAbandonedTimeoutMillis(removeAbandonedTimeoutMillis); ds.setLogAbandoned(logAbandoned); // 只要maxPoolPreparedStatementPerConnectionSize>0,poolPreparedStatements就會被自動設定為true,參照druid的原始碼 ds.setMaxPoolPreparedStatementPerConnectionSize(parent .getMaxPoolPreparedStatementPerConnectionSize()); if (StringUtils.isNotBlank(filters)) try { ds.setFilters(filters); } catch (SQLException e) { throw new RuntimeException(e); } addFilterList(ds); return ds; } private void addFilterList(DruidDataSource ds) { if (filterList != null) { List<Filter> targetList = ds.getProxyFilters(); for (Filter add : filterList) { boolean found = false; for (Filter target : targetList) { if (add.getClass().equals(target.getClass())) { found = true; break; } } if (!found) targetList.add(add); } } } }
二、修改配置檔案
主要是修改spring-mybatis.xml
<!-- 配置資料來源 --> <bean name="defaultDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="${jdbc_url}"/> <property name="username" value="${jdbc_username}"/> <property name="password" value="${jdbc_password}"/> <!-- 初始化連線大小 --> <property name="initialSize" value="0"/> <!-- 連線池最大使用連線數量 --> <property name="maxActive" value="20"/> <!-- 連線池最大空閒 --> <property name="maxIdle" value="20"/> <!-- 連線池最小空閒 --> <property name="minIdle" value="0"/> <!-- 獲取連線最大等待時間 --> <property name="maxWait" value="60000"/> <property name="validationQuery" value="${validationQuery}"/> <property name="testOnBorrow" value="false"/> <property name="testOnReturn" value="false"/> <property name="testWhileIdle" value="true"/> <!-- 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連線,單位是毫秒 --> <property name="timeBetweenEvictionRunsMillis" value="60000"/> <!-- 配置一個連線在池中最小生存的時間,單位是毫秒 --> <property name="minEvictableIdleTimeMillis" value="25200000"/> <!-- 開啟removeAbandoned功能 --> <property name="removeAbandoned" value="true"/> <!-- 1800秒,也就是30分鐘 --> <property name="removeAbandonedTimeout" value="1800"/> <!-- 關閉abanded連線時輸出錯誤日誌 --> <property name="logAbandoned" value="true"/> <!-- 監控資料庫 --> <property name="filters" value="mergeStat"/> </bean> <bean id="druidDynamicDataSource" class="com.blog.datasource.DruidDynamicDataSource"> <property name="defaultTargetDataSource" ref="defaultDataSource" /> <property name="targetDataSources"> <map> <entry key="defaultDataSource" value-ref="defaultDataSource"/> <!-- 這裡還可以加多個dataSource --> </map> </property> </bean> <!-- Spring整合Mybatis,更多檢視文件:http://mp.baomidou.com --> <bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean"> <property name="dataSource" ref="druidDynamicDataSource" /> <!-- 自動掃描Mapping.xml檔案 --> <property name="mapperLocations" value="classpath:mybatis/system/*.xml"/> <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/> <property name="typeAliasesPackage" value="com.blog.entity"/> <property name="plugins"> <array> <!-- 分頁外掛配置 --> <bean id="paginationInterceptor" class="com.baomidou.mybatisplus.plugins.PaginationInterceptor"> </bean> </array> </property> <!-- 全域性配置注入 --> <property name="globalConfig" ref="globalConfig" /> </bean> <!-- 配置事務管理 --> <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="druidDynamicDataSource"/> </bean>
三、單元測試
import java.util.HashMap; import java.util.List; import java.util.Map; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.blog.datasource.DBContextHolder; import com.blog.entity.User; import com.blog.mapper.PostDao; import com.blog.service.UserService; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:spring/spring.xml") public class BlogTest { @Autowired private UserService ud; @Test public void testName() throws Exception { Map<String, Object> map = new HashMap<String, Object>(); map.put(DBContextHolder.DATASOURCE_KEY, "localhost"); map.put(DBContextHolder.DATASOURCE_DRIVER, "com.mysql.jdbc.Driver"); map.put(DBContextHolder.DATASOURCE_URL, "jdbc:mysql://127.0.0.1:3306/blog_test?useUnicode=true&characterEncoding=UTF-8"); map.put(DBContextHolder.DATASOURCE_USERNAME, "root"); map.put(DBContextHolder.DATASOURCE_PASSWORD, "1234"); DBContextHolder.setDBType(map); List<User> list = ud.selectList(null); for (User user : list) { System.out.println(user); } } }
測試後,控制檯如圖:
小結:
其實配置多資料來源有很多方式,有aop,也有配置多個bean的方式,當然了,只要能達到目的就是王道,當然了,我也強調一點,不是實現完就不管了,背後的為什麼比只要實現就好更重要。
其實,有一點我想說的是,有些時候遇到難題,最好的方式是迎面而上解決這個問題,而不是逃避或者獨自焦躁。同時直面問題,也是解決焦躁的最好方式。這個我已經深有體會了。
另外補充到,上傳至github上的多資料來源示例同時也是ssm框架的搭建。有哪位朋友不會搭建框架,可以參考我的這個。希望能對你們有什麼幫助。