Spring AOP根據JdbcTemplate方法名動態設置數據源

說明:如今的場景是,採用數據庫(Mysql)複製(binlog)的方式在兩臺不一樣服務器部署並配置主從(Master-Slave)關係; 並須要程序上的數據操做方法來訪問不一樣的數據庫,好比,update方法訪問主數據庫服務器,query方法訪問從數據庫服務器。 即把「增刪改」和「查」分開訪問兩臺服務器,固然兩臺服務器的數據庫同步事先已經配置好。 然而程序是早已完成的使用Spring JdbcTemplate的架構,如何在不修改任何源代碼的狀況下達到<本文標題>的功能呢? 分析: 1.目前有兩個數據源須要配置到Spring框架中,如何統一管理這兩個數據源? JdbcTemplate有不少數據庫操做方法,關鍵的能夠分爲如下幾類(使用簡明通配符):execute(args..)、update(args..)、batchUpdate(args..)、query*(args..) #args.. 表示可爲任意個參數或無參數。 2.如何根據這些方法名來使用不一樣的數據源呢? 3.多數據源的事務管理(非本文關注點)。 實現: Spring配置文件applicationContext.xml(包含相關bean類的代碼) 1.數據源配置(省略了更爲詳細的鏈接參數設置): 01 <bean id="masterDataSource" 02 class="org.apache.commons.dbcp.BasicDataSource" 03 destroy-method="close"> 04 <property name="driverClassName" 05 value="${jdbc.driverClassName}" /> 06 <property name="url" value="${jdbc.url}" /> 07 <property name="username" value="${jdbc.username}" /> 08 <property name="password" value="${jdbc.password}" /> 09 <property name="poolPreparedStatements" value="true" /> 10 <property name="defaultAutoCommit" value="true" /> 11 </bean> 12 <bean id="slaveDataSource" 13 class="org.apache.commons.dbcp.BasicDataSource" 14 destroy-method="close"> 15 <property name="driverClassName" 16 value="${jdbc.driverClassName}" /> 17 <property name="url" value="${jdbc.url2}" /> 18 <property name="username" value="${jdbc.username}" /> 19 <property name="password" value="${jdbc.password}" /> 20 <property name="poolPreparedStatements" value="true" /> 21 <property name="defaultAutoCommit" value="true" /> 22 </bean> 23 <bean id="dataSource" 24 class="test.my.serivce.ds.DynamicDataSource"> 25 <property name="targetDataSources"> 26 <map> 27 <entry key="master" value-ref="masterDataSource" /> 28 <entry key="slave" value-ref="slaveDataSource" /> 29 </map> 30 </property> 31 <property name="defaultTargetDataSource" ref="masterDataSource" /> 32 </bean> 首先定義兩個數據源(鏈接地址及用戶名等數據存放在properties屬性文件中),Spring能夠設置多個數據源,究其根本也不過是一個普通bean罷了。 關鍵是ID爲「dataSource」的這個bean的設置,它是這個類「test.my.serivce.ds.DynamicDataSource」的一個實例: 1 import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; 2 3 public class DynamicDataSource extends AbstractRoutingDataSource { 4 @Override 5 protected Object determineCurrentLookupKey() { 6 return CustomerContextHolder.getCustomerType(); 7 } 8 }html

DynamicDataSource類繼承了Spring的抽象類AbstractRoutingDataSource,而AbstractRoutingDataSource自己實現了javax.sql.DataSource接口(由其父類抽象類AbstractDataSource實現),所以其實際上也是一個標準數據源的實現類。該類是Spring專爲多數據源管理而增長的一個接口層,參見Spring-api-doc可知: Abstract DataSource implementation that routes getConnection() calls to one of various target DataSources based on a lookup key. The latter is usually (but not necessarily) determined through some thread-bound transaction context. 它根據一個數據源惟一標識key來尋找已經配置好的數據源隊列,它一般是與當前線程綁定在一塊兒的。 查看其源碼,知道它還實現了Spring的初始化方法類InitializingBean,這個類只有一個方法:afterPropertiesSet(),由Spring在初始化bean完成以後調用(根據該方法名聯想應該是設置完全部屬性後再調用,實際也是如此): 01 public void afterPropertiesSet() { 02 if (this.targetDataSources == null) { 03 throw new IllegalArgumentException("targetDataSources is required"); 04 } 05 this.resolvedDataSources = new HashMap(this.targetDataSources.size()); 06 for (Iterator it = this.targetDataSources.entrySet().iterator(); it.hasNext(); ) { 07 Map.Entry entry = (Map.Entry)it.next(); 08 Object lookupKey = resolveSpecifiedLookupKey(entry.getKey()); 09 DataSource dataSource = resolveSpecifiedDataSource(entry.getValue()); 10 this.resolvedDataSources.put(lookupKey, dataSource); 11 } 12 if (this.defaultTargetDataSource != null) 13 this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource); 14 } 查看其具體實現可知,Spring將全部已經配置好的數據源存放到一個名爲targetDataSources的hashMap對象中(targetDataSources屬性必須設置,不然異常;defaultTargetDataSource屬性能夠沒必要設置)。只是把數據源統一存到一個map中並不能作什麼,關鍵是它還重寫了javax.sql.DataSource的getConnection()方法,該方法不管你在什麼時候使用數據庫操做相關的方法時都會使用到,即便ibatis、hibernate、JPA等進行多層封裝的框架底層仍是使用最普通的JDBC來實現。 01 public Connection getConnection() throws SQLException { 02 return determineTargetDataSource().getConnection(); 03 } 04 protected DataSource determineTargetDataSource() { 05 Object lookupKey = determineCurrentLookupKey(); 06 DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey); 07 if (dataSource == null) 08 dataSource = this.resolvedDefaultDataSource; 09 if (dataSource == null) 10 throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]"); 11 return dataSource; 12 } 13 protected Object resolveSpecifiedLookupKey(Object lookupKey) { 14 return lookupKey; 15 } 16 protected abstract Object determineCurrentLookupKey(); 省略部分校驗代碼,這裏有一個必須的關鍵方法:determineCurrentLookupKey,也是一個抽象的有你本身實現的方法,從這個方法返回實際要使用的數據源的key(也即在前面配置的數據源bean的ID)。從Spring-api-doc中能夠看到詳細說明: Determine the current lookup key. This will typically be implemented to check a thread-bound transaction context. Allows for arbitrary keys. The returned key needs to match the stored lookup key type. 它容許任意類型的key,但必須是跟保存到數據源hashMap中的key類型一致。咱們能夠在Spring配置文件中指定該類型,網上看到有仁兄使用枚舉類型的,是一個有不錯約束性的主意。 咱們的「test.my.serivce.ds.DynamicDataSource」實現了這個方法,可見具體的數據源key是從CustomerContextHolder類中得到的,而且也是使用key與當前線程綁定的方式: 01 public class CustomerContextHolder { 02 private static final ThreadLocal contextHolder = new ThreadLocal(); 03 public static void setCustomerType(String customerType) { 04 contextHolder.set(customerType); 05 } 06 public static String getCustomerType() { 07 return (String) contextHolder.get(); 08 } 09 public static void clearCustomerType() { 10 contextHolder.remove(); 11 } 12 } 咱們也可使用全局變量的方式來存儲這個key。我以前並不知道java.lang.ThreadLocal,那就充點電吧:http://java.dzone.com/articles/java-thread-local-–-how-use 甚至有一位評論者一針見血的指出問題來: Why is userThreadLocal declared public? AFAIK, ThreadLocal instances are typically private static fields. Also, ThreadLocal is a generic type, it is ThreadLocal<T>. An important benefit of ThreadLocal worth mentioning (from 1.4 JVMs forward), is as an alternative to synchronization, to improve scalability in transaction-intensive environments. Classes encapsulated in ThreadLocal are automatically thread-safe in a pretty simple way, since it's clear that anything stored in ThreadLocal is not shared between threads. ThreadLocal是線程安全的,而且不能在多線程之間共享。根據這個原理,我寫了下面的小例子以便進一步理解: 01 public class Test { 02 private static ThreadLocal tl = new ThreadLocal(); 03 public static void main(String[] args) { 04 tl.set("abc"); 05 System.out.println(tl.get()); 06 new Thread(new Runnable() { 07 public void run() { 08 System.out.println(tl.get()); 09 } 10 }).start(); 11 } 12 } 作到這裏,咱們已經解決了第一個問題,但彷佛尚未進入正題,如何根據JdbcTemplate方法名動態設置數據源呢? 2.Spring AOP切入JdbcTemplate方法配置: 01 <bean id="ba" class="test.my.serivce.ds.BeforeAdvice" /> 02 <aop:config proxy-target-class="true"> 03 <aop:aspect ref="ba"> 04 <aop:pointcut id="update" 05 expression="execution(* org.springframework.jdbc.core.JdbcTemplate.update*(..)) || execution(* org.springframework.jdbc.core.JdbcTemplate.batchUpdate(..))" /> 06 <aop:before method="setMasterDataSource" 07 pointcut-ref="update" /> 08 </aop:aspect> 09 <aop:aspect ref="ba"> 10 <aop:before method="setSlaveDataSource" 11 pointcut="execution(* org.springframework.jdbc.core.JdbcTemplate.query*(..)) || execution(* org.springframework.jdbc.core.JdbcTemplate.execute(..))" /> 12 </aop:aspect> 13 </aop:config> 能夠看到我已經使用aop:aspect將JdbcTemplate的4類方法進行攔截,並使用前置通知的方式(aop:before)在執行這些方法以前調用其餘方法,具體的AOP表達式語言的含義我就不細說了。 根據最開始的說明,分別對update操做和select操做進行攔截並調用不一樣的方法,這個方法究竟是什麼呢? 其實就是給ThreadLocal設置數據源的名字(key),以便DynamicDataSource知道究竟是使用哪個數據源。java

前置方法就是調用「test.my.serivce.ds.BeforeAdvice」類的某個set方法: 1 public class BeforeAdvice { 2 public void setMasterDataSource() { 3 CustomerContextHolder.setCustomerType("master"); 4 } 5 public void setSlaveDataSource() { 6 CustomerContextHolder.setCustomerType("slave"); 7 } 8 } 當前線程就會保存下設置進去的key名稱並隨時能夠調用。 最後再配置一個JdbcTemplate bean便可。 1 <bean id="jdbcTemplate" 2 class="org.springframework.jdbc.core.JdbcTemplate"> 3 <property name="dataSource" ref="dataSource" /> 4 </bean> 附註: 1.在解決過程當中遇到的一個問題: Spring異常:no matching editors or conversion strategy found 參考:http://blog.csdn.net/zzycgfans/article/details/6316081 引用:Spring注入的是接口,關聯的是實現類。 這裏注入了實現類,因此報異常了。 2.本文主要參考的文章有: 該文還包含事務管理的配置:http://hi.baidu.com/litianyi520/blog/item/71279e3e180db6f1838b1327.html 該文與多數據源的設置對我有必定的啓發(此外還包含測試用例):http://oiote.blog.sohu.com/74596942.html 以前作過ibatis採用ehCache和osCache作緩存的配置,這篇有點相似:http://lihaiyan.iteye.com/blog/127818 多數據源的一些實際場景分析,理論重於實際:http://hi.baidu.com/freeway2000/blog/item/ba0906f4946fa8eb7709d716.html 此外,javaeye(現爲iteye)的一些文章也是有參考價值的:http://www.iteye.com/wiki/blog/1229655 EOF.最初的設想到這裏變成了現實。本文講述了「Spring AOP根據JdbcTemplate方法名動態設置數據源」的整個實現過程和一些淺顯的分析。 使用這樣配置後在實際使用中發現仍然有問題。好比,調用jdbcTemplate的update方法後當即調用query方法查詢該條記錄,或者使用如下方法: this.jdbcTemplate.update(new PreparedStatementCreator(), keyHolder) 由於數據庫複製有同步間隙,這個時間晚於程序的調用,就會出現查詢不到數據的狀況,其實是數據還未同步到從服務器。期待更好的解決方案!spring

相關文章
相關標籤/搜索