文档章节

Spring AOP根据JdbcTemplate方法名动态设置数据源

happy圈圈
 happy圈圈
发布于 2014/08/26 15:51
字数 2115
阅读 180
收藏 8

说明:现在的场景是,采用数据库(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 }

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知道到底是使用哪一个数据源。

前置方法就是调用“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) 因为数据库复制有同步间隙,这个时间晚于程序的调用,就会出现查询不到数据的情况,实际上是数据还未同步到从服务器。期待更好的解决方案!

本文转载自:http://blog.sina.com.cn/s/blog_496782b1010100u1.html

happy圈圈
粉丝 1
博文 32
码字总数 24749
作品 0
朝阳
私信 提问
Spring AOP根据JdbcTemplate方法名动态设置数据源

说明:现在的场景是,采用MySQL Replication的方式在两台不同服务器部署并配置主从(Master-Slave)复制; 并需要程序上的数据操作方法访问不同的数据库,比如,update方法访问主数据库服务器...

cwalet
2011/11/28
6.1K
9
Spring中用了哪些设计模式?

设计模式作为工作学习中的枕边书,却时常处于勤说不用的尴尬境地,也不是我们时常忘记,只是一直没有记忆。Spring作为业界的经典框架,无论是在架构设计方面,还是在代码编写方面,都堪称行内...

HOT_POT
02/17
91
0
转载:MySQL的内存表在主从同步的注意事项

前言:前天不是做了Spring AOP根据JdbcTemplate方法名动态设置数据源吗,今天发现mysql复制有问题没解决: 那就是内存表复制的问题,发现重启后从数据库的表结构还在,但是无法同步主数据库的...

cwalet
2011/11/29
303
0
spring源码-详解设计模式

spring中常用到的设计模式有九种,以下举例: 第一种:简单工厂(StaticFactory Method) spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标志来获得bean对象,但是否是在...

__HuWei
03/22
28
0
这些 Spring 中的设计模式,你都知道吗?

导读:设计模式作为工作学习中的枕边书,却时常处于勤说不用的尴尬境地,也不是我们时常忘记,只是一直没有记忆。Spring作为业界的经典框架,无论是在架构设计方面,还是在代码编写方面,都堪...

一看就喷亏的小猿
04/04
25
0

没有更多内容

加载失败,请刷新页面

加载更多

Handler消息传递机制分析

Handler的用途和用法 写过Android程序的人大概都会遇到ANR(Application Not Responding)。如果程序在一段时间内没有响应,系统就会弹出一个对话框,让用户选择继续等待还是强制关闭应用。为...

tommwq
今天
5
0
JS前端MD5加密

Bootstrap官网获得md5 js地址:https://www.bootcdn.cn/blueimp-md5/ <!--MD5加密--><script src="https://cdn.bootcss.com/blueimp-md5/2.10.0/js/md5.js"></script> 使用方法: md5(pwd)......

被毒打的程序猿_先瑞
今天
6
0
BigDecimal 去后面无用的0的方法

BigDecimal a=new BigDecimal("0.1000"); System.out.println(a.stripTrailingZeros().toPlainString());...

xiaodong16
今天
7
0
JAVA--高级基础开发

[集合版双色球] 十二、双色球规则:双色球每注投注号码由6个红色球号码和1个蓝色球号码组成。红色球号码从1—33中选择;蓝色球号码从1—16中选择;请随机生成一注双色球号码。(要求同色号码...

李文杰-yaya
昨天
25
0
聊聊rocketmq broker的CONSUMER_SEND_MSG_BACK

序 本文主要研究一下rocketmq broker的CONSUMER_SEND_MSG_BACK CONSUMER_SEND_MSG_BACK rocketmq/common/src/main/java/org/apache/rocketmq/common/protocol/RequestCode.java public class......

go4it
昨天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部