Spring動態配置多數據源,即在大型應用中對數據進行切分,並且采用多個數據庫實例進行管理,這樣可以有效提高系統的水平伸縮性。而這樣的方案就會不同於常見的單一數據實例的方案,這就要程序在運行時根據當時的請求及系統狀態來動態的決定將數據存儲在哪個數據庫實例中,以及從哪個數據庫提取數據。
Spring配置多數據源的方式和具體使用過程。
Spring對於多數據源,以數據庫表為參照,大體上可以分成兩大類情況:
一是,表級上的跨數據庫。即,對於不同的數據庫卻有相同的表(表名和表結構完全相同)。
二是,非表級上的跨數據庫。即,多個數據源不存在相同的表。
1、根據用戶的選擇,使用不同的數據源。
2、解決思路鎖定:將sessionFactory的屬性dataSource設置成不同的數據源,以達到切換數據源的目的。
3、問題產生:因為整個項目用的幾乎都是單例模式,當多個用戶並發訪問數據庫的時候,會產生資源爭奪的問題。即項目啟動時候,所有的bean都被裝載到內存,並且每個bean都只有一個對象。正因為只有一個對象,所有的對象屬性就如同靜態變量(靜態變量跟單例很相似,常用靜態來實現單例)。整個項目系統的dataSource只有一個,如果很多用戶不斷的去改變dataSource的值,那必然會出現資源的掠奪問題,造成系統隱患。
4、多資源共享解決思路:同一資源被搶奪的時候,通常有兩種做法,a、以時間換空間 b、以空間換時間。
5、線程同步機制就是典型的“以時間換空間”,采用排隊稍等的方法,一個個等待,直到前面一個用完,后面的才跟上,多人共用一個變量,用synchronized鎖定排隊。
6、“ThreadLocal”就是典型的“以空間換時間”,她可以為每一個人提供一份變量,因此可以同時訪問並互不干擾。
7、言歸正傳:sessionFactory的屬性dataSource設置成不用的數據源,首先不能在配置文件中寫死,我們必須為她單獨寫一個類,讓她來引用這個類,在這個類中再來判斷我們到底要選擇哪個數據源。
spring + mybatis 多數據源切換
DbContextHolder.java
1 package com.easyway.stage.commons; 2 3 public class DbContextHolder 4 { 5 6 // ThreadLocal是線程安全的,並且不能在多線程之間共享。 7 private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); 8 9 public static void setDbType(String dbType) 10 { 11 contextHolder.set(dbType); 12 } 13 14 public static String getDbType() 15 { 16 return ((String) contextHolder.get()); 17 } 18 19 public static void clearDbType() 20 { 21 contextHolder.remove(); 22 } 23 24 }
MultiDataSource.java
1 package com.easyway.stage.commons; 2 3 import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; 4 5 public class MultiDataSource extends AbstractRoutingDataSource 6 { 7 8 @Override 9 protected Object determineCurrentLookupKey() 10 { 11 return DbContextHolder.getDbType(); 12 } 13 14 }
Xml代碼:
applicationContext.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans 3 xmlns="http://www.springframework.org/schema/beans" 4 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" 5 xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jdbc="http://www.springframework.org/schema/jdbc" 6 xmlns:context="http://www.springframework.org/schema/context" 7 xsi:schemaLocation=" 8 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 9 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd 10 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd 11 http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd 12 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> 13 14 <context:annotation-config/> 15 16 <!-- 數據源 --> 17 <bean id="parentDataSource" class="com.alibaba.druid.pool.DruidDataSource"> 18 <!-- 配置初始化大小、最小、最大 --> 19 <property name="initialSize" value="1" /> 20 <property name="maxActive" value="20" /> 21 <property name="minIdle" value="1" /> 22 23 <!-- 配置獲取連接等待超時的時間60s --> 24 <property name="maxWait" value="60000" /> 25 26 <!-- 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒 --> 27 <property name="timeBetweenEvictionRunsMillis" value="60000" /> 28 <!-- 配置一個連接在池中最小生存的時間,單位是毫秒 --> 29 <property name="minEvictableIdleTimeMillis" value="300000" /> 30 31 <property name="validationQuery" value="SELECT 'x'" /> 32 <property name="testWhileIdle" value="true" /> 33 <property name="testOnBorrow" value="false" /> 34 <property name="testOnReturn" value="false" /> 35 36 <!-- 打開PSCache,並且指定每個連接上PSCache的大小 --> 37 <property name="poolPreparedStatements" value="true" /> 38 <property name="maxPoolPreparedStatementPerConnectionSize" value="20" /> 39 40 <!-- 配置監控統計攔截的filters --> 41 <property name="filters" value="wall,stat,slf4j" /> 42 43 <!-- 對於長時間不使用的連接強制關閉 --> 44 <property name="removeAbandoned" value="true" /> 45 <!-- 超過30分鍾開始關閉空閑連接 --> 46 <property name="removeAbandonedTimeout" value="1800" /> 47 <!-- 關閉abanded連接時輸出錯誤日志 --> 48 <property name="logAbandoned" value="true" /> 49 </bean> 50 <bean id="local" parent="parentDataSource" init-method="init" destroy-method="close"> 51 <property name="url" value="${local.jdbc.url}" /> 52 <property name="username" value="${local.jdbc.username}" /> 53 <property name="password" value="${local.jdbc.password}" /> 54 </bean> 55 <bean id="server" parent="parentDataSource" init-method="init" destroy-method="close"> 56 <property name="url" value="${jdbc.url}" /> 57 <property name="username" value="${jdbc.username}" /> 58 <property name="password" value="${jdbc.password}" /> 59 </bean> 60 61 <bean id="dataSource" class="com.autrade.stage.commons.MultiDataSource"> 62 <property name="targetDataSources"> 63 <map key-type="java.lang.String"> 64 <entry value-ref="local" key="local"></entry> 65 <entry value-ref="server" key="server"></entry> 66 </map> 67 </property> 68 <!-- 默認使用server的數據源 --> 69 <property name="defaultTargetDataSource" ref="server"></property> 70 </bean> 71 72 <!-- MyBatis --> 73 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> 74 <property name="dataSource" ref="dataSource" /> 75 <property name="configLocation" value="classpath:resources/mybatis/myBatisConfig.xml" /> 76 <property name="mapperLocations" value="classpath:resources/mybatis/mapper/*.xml"/> 77 </bean> 78 <bean class="org.mybatis.spring.SqlSessionTemplate"> 79 <constructor-arg ref="sqlSessionFactory"/> 80 </bean> 81 <!-- MyBatis --> 82 83 <!-- 配置事務管理對象--> 84 <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 85 <property name="dataSource" ref="dataSource"/> 86 </bean> 87 <!-- 將所有具有@Transactional注解的Bean自動配置為聲明式事務支持 --> 88 <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/> 89 90 <!-- 自定義的攔截器 --> 91 <bean id="methodAdvisor" class="com.easyway.app.interceptor.InjectorManager" /> 92 93 <aop:config proxy-target-class="true"> 94 <aop:pointcut id="baseMethods" 95 expression="execution(* com.easyway.app.service..*.*(..))" /> 96 <aop:advisor advice-ref="methodAdvisor" pointcut-ref="baseMethods" /> 97 </aop:config> 98 99 </beans>
Test.java測試類
1 package com.easyway.stage.test; 2 3 import javax.sql.DataSource; 4 5 import org.apache.ibatis.session.SqlSession; 6 import org.apache.ibatis.session.SqlSessionFactory; 7 import org.mybatis.spring.SqlSessionFactoryBean; 8 import org.springframework.context.ApplicationContext; 9 import org.springframework.context.support.ClassPathXmlApplicationContext; 10 import org.springframework.core.io.FileSystemResource; 11 import org.springframework.core.io.Resource; 12 13 import com.easyway.stage.commons.DbContextHolder; 14 15 public class Test 16 { 17 18 /** 19 * @param args 20 */ 21 public static void main(String[] args) 22 { 23 ApplicationContext appContext = new ClassPathXmlApplicationContext("client-beans.xml"); 24 25 DbContextHolder.setDbType("local"); 26 String res = "resources/mybatis/myBatisConfig.xml"; 27 DataSource datasource = (DataSource) appContext.getBean("dataSource"); 28 29 SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); 30 bean.setDataSource(datasource); 31 Resource resource = new FileSystemResource(res); 32 bean.setConfigLocation(resource); 33 try 34 { 35 SqlSessionFactory sessionfactory = bean.getObject(); 36 SqlSession session = sessionfactory.openSession(); 37 User user = session.selectOne("com.easyway.mybatis.mapper.findOne"); 38 System.out.println(user.getName()); 39 } 40 catch (Exception e) 41 { 42 e.printStackTrace(); 43 } 44 45 DbContextHolder.setDbType("server"); 46 String res1 = "resources/mybatis/myBatisConfig.xml"; 47 DataSource datasource1 = (DataSource) appContext.getBean("dataSource"); 48 49 SqlSessionFactoryBean bean1 = new SqlSessionFactoryBean(); 50 bean1.setDataSource(datasource1); 51 Resource resource1 = new FileSystemResource(res1); 52 bean1.setConfigLocation(resource1); 53 54 try 55 { 56 SqlSessionFactory sessionfactory = bean.getObject(); 57 SqlSession session = sessionfactory.openSession(); 58 User user = session.selectOne("com.easyway.mybatis.mapper.findOne"); 59 System.out.println(user.getName()); 60 } 61 catch (Exception e) 62 { 63 e.printStackTrace(); 64 } 65 66 } 67 68 }
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Spring動態配置多數據源,即在大型應用中對數據進行切分,並且采用多個數據庫實例進行管理,這樣可以有效提高系統的水平伸縮性。而這樣的方案就會不同於常見的單一數據實例的方案,這就要程序在運行時根據當時的請求及系統狀態來動態的決定將數據存儲在哪個數據庫實例中,以及從哪個數據庫提取數據。
Spring2.x以后的版本中采用Proxy模式,就是我們在方案中實現一個虛擬的數據源,並且用它來封裝數據源選擇邏輯,這樣就可以有效地將數據源選擇邏輯從Client中分離出來。Client提供選擇所需的上下文(因為這是Client所知道的),由虛擬的DataSource根據Client提供的上下文來實現數據源的選擇。
實現
- /**
- * 動態配置多數據源
- * 數據源的名稱常量類
- * @author LONGHUI_LUO
- *
- */
- public class DataSourceConst {
- public static final String TEST="test";
- public static final String USER="User";
- }
2. 建立一個獲得和設置上下文環境的類,主要負責改變上下文數據源的名稱:
- /**
- * 獲得和設置上下文環境 主要負責改變上下文數據源的名稱
- *
- * @author LONGHUI_LUO
- *
- */
- public class DataSourceContextHolder {
- private static final ThreadLocal contextHolder = new ThreadLocal(); // 線程本地環境
- // 設置數據源類型
- public static void setDataSourceType(String dataSourceType) {
- contextHolder.set(dataSourceType);
- }
- // 獲取數據源類型
- public static String getDataSourceType() {
- return (String) contextHolder.get();
- }
- // 清除數據源類型
- public static void clearDataSourceType() {
- contextHolder.remove();
- }
- }
3. 建立動態數據源類,注意,這個類必須繼承AbstractRoutingDataSource,且實現方法determineCurrentLookupKey,該方法返回一個Object,一般是返回字符串:
- /**
- * 建立動態數據源
- *
- * @author LONGHUI_LUO
- *
- */
- public class DynamicDataSource extends AbstractRoutingDataSource {
- protected Object determineCurrentLookupKey() {
- // 在進行DAO操作前,通過上下文環境變量,獲得數據源的類型
- return DataSourceContextHolder.getDataSourceType();
- }
- }
4. 編寫spring的配置文件配置多個數據源
- <!-- 數據源相同的內容 -->
- <bean
- id="parentDataSource"
- class="org.apache.commons.dbcp.BasicDataSource"
- destroy-method="close">
- <property
- name="driverClassName"
- value="com.microsoft.sqlserver.jdbc.SQLServerDriver" />
- <property name="username" value="sa" />
- <property name="password" value="net2com" />
- </bean>
- <!-- start以下配置各個數據源的特性 -->
- <bean parent="parentDataSource" id="testDataSource">
- <propertynamepropertyname="url" value="jdbc:sqlserver://localhost:1433;databaseName=test" />
- </bean>
- <bean parent="parentDataSource" id="UserDataSource">
- <property
- name="url"
- value="jdbc:sqlserver://localhost:1433;databaseName=User" />
- </bean>
- <!-- end 配置各個數據源的特性 -->
5. 編寫spring配置文件配置多數據源映射關系
- <bean class="com.xxxx.datasouce.DynamicDataSource" id="dataSource">
- <property name="targetDataSources">
- <map key-type="java.lang.String">
- <entry value-ref="testDataSource" key="test"></entry>
- <entry value-ref="UserDataSource" key="User"></entry>
- </map>
- </property>
- <property name="defaultTargetDataSource" ref="testDataSource" ></property>
- </bean>
在這個配置中第一個property屬性配置目標數據源,<map key-type="Java.lang.String">中的key-type必須要和靜態鍵值對照類DataSourceConst中的值的類型相 同;<entry key="User" value-ref="userDataSource"/>中key的值必須要和靜態鍵值對照類中的值相同,如果有多個值,可以配置多個< entry>標簽。第二個property屬性配置默認的數據源。
動態切換是數據源
- DataSourceContextHolder.setDataSourceType(DataSourceConst.TEST);
