我们今天的主角是AbstractRoutingDataSource,在Spring2.0.1发布之后,引入了AbstractRoutingDataSource,使用该类可以实现普遍意义上的多数据源管理功能。

1、扩展Spring的AbstractRoutingDataSource抽象类(该类充当了DataSource的路由中介, 能有在运行时, 根据某种key值来动态切换到真正的DataSource上。)

从AbstractRoutingDataSource的源码中:

  1. public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean

我们可以看到,它继承了AbstractDataSource,而AbstractDataSource不就是javax.sql.DataSource的子类,So我们可以分析下它的getConnection方法:

  1. public Connection getConnection() throws SQLException {
  2. return determineTargetDataSource().getConnection();
  3. }
  4.  
  5. public Connection getConnection(String username, String password) throws SQLException {
  6. return determineTargetDataSource().getConnection(username, password);
  7. }

获取连接的方法中,重点是determineTargetDataSource()方法,看源码:

  1. /**
  2. * Retrieve the current target DataSource. Determines the
  3. * {@link #determineCurrentLookupKey() current lookup key}, performs
  4. * a lookup in the {@link #setTargetDataSources targetDataSources} map,
  5. * falls back to the specified
  6. * {@link #setDefaultTargetDataSource default target DataSource} if necessary.
  7. * @see #determineCurrentLookupKey()
  8. */
  9. protected DataSource determineTargetDataSource() {
  10. Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
  11. Object lookupKey = determineCurrentLookupKey();
  12. DataSource dataSource = this.resolvedDataSources.get(lookupKey);
  13. if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
  14. dataSource = this.resolvedDefaultDataSource;
  15. }
  16. if (dataSource == null) {
  17. throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
  18. }
  19. return dataSource;
  20. }

上面这段源码的重点在于determineCurrentLookupKey()方法,这是AbstractRoutingDataSource类中的一个抽象方法,而它的返回值是你所要用的数据源dataSource的key值,有了这个key值,resolvedDataSource(这是个map,由配置文件中设置好后存入的)就从中取出对应的DataSource,如果找不到,就用配置默认的数据源。

看完源码,应该有点启发了吧,没错!你要扩展AbstractRoutingDataSource类,并重写其中的determineCurrentLookupKey()方法,来实现数据源的切换:

  1. public class DynamicDataSource extends AbstractRoutingDataSource{
  2.  
  3. @Override
  4. protected Object determineCurrentLookupKey() {
  5. return DBContextHolder.getDBType();
  6. }
  7. }

DataSourceHolder这个类则是我们自己封装的对数据源进行操作的类:

  1. public class DataSourceHolder {
  2. //线程本地环境
  3. private static final ThreadLocal<String> dataSources = new ThreadLocal<String>();
  4. //设置数据源
  5. public static void setDataSource(String customerType) {
  6. dataSources.set(customerType);
  7. } //获取数据源
  8. public static String getDataSource() {
  9. return (String) dataSources.get();
  10. } //清除数据源
  11. public static void clearDataSource() {
  12. dataSources.remove();
  13. }
  14.  
  15. }

setDataSource如何使用呢?我们可以在程序里面自己写:

  1. ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  2. BaseDAO dao = (BaseDAO) context.getBean("sqlBaseDAO", BaseDAOImpl.class);
  3.  
  4. try {
  5. DataSourceHolder.setDataSource("data1");
  6. System.err.println(dao.select("select count(*) sum from TEST t ").get(0).get("SUM"));
  7. DataSourceHolder.setDataSource("data2");
  8. System.err.println(dao.select("select count(*) sum from TEST t ").get(0).get("SUM"));
  9.  
  10. } catch (Exception e) {
  11. e.printStackTrace();
  12. } finally{
  13. DataSourceHolder.clearDataSource();
  14. }

也可以用更优雅的方式aop,把配置的数据源类型都设置成为注解标签,在service层中需要切换数据源的方法上,写上注解标签,调用相应方法切换数据源:

  1. @DataSource(name=DataSource.slave1)
  2. public List getProducts(){
  3. }
  1. import java.lang.annotation.*;
  2.  
  3. @Target({ElementType.METHOD, ElementType.TYPE})
  4. @Retention(RetentionPolicy.RUNTIME)
  5. @Documentedpublic @interface DataSource {
  6. String name() default DataSource.master;
  7. public static String master = "dataSource1";
  8. public static String slave1 = "dataSource2";
  9. public static String slave2 = "dataSource3";
  10. }

有时候我们可能要实现的功能是读写分离,要求select走一个库,update,delete走一个库,我们有了规则就不想每个方法都去写注解了

我们先把spring配置文件搞上:

  1. <bean id = "dataSource1" class = "com.mysql.jdbc.jdbc2.optional.MysqlDataSource">
  2. <property name="url" value="${db1.url}"/>
  3. <property name = "user" value = "${db1.user}"/>
  4. <property name = "password" value = "${db1.pwd}"/>
  5. <property name="autoReconnect" value="true"/>
  6. <property name="useUnicode" value="true"/>
  7. <property name="characterEncoding" value="UTF-8"/>
  8. </bean>
  9.  
  10. <bean id = "dataSource2" class = "com.mysql.jdbc.jdbc2.optional.MysqlDataSource">
  11. <property name="url" value="${db2.url}"/>
  12. <property name = "user" value = "${db2.user}"/>
  13. <property name = "password" value = "${db2.pwd}"/>
  14. <property name="autoReconnect" value="true"/>
  15. <property name="useUnicode" value="true"/>
  16. <property name="characterEncoding" value="UTF-8"/>
  17. </bean>
  18.  
  19. <bean id = "dataSource3" class = "com.mysql.jdbc.jdbc2.optional.MysqlDataSource">
  20. <property name="url" value="${db3.url}"/>
  21. <property name = "user" value = "${db3.user}"/>
  22. <property name = "password" value = "${db3.pwd}"/>
  23. <property name="autoReconnect" value="true"/>
  24. <property name="useUnicode" value="true"/>
  25. <property name="characterEncoding" value="UTF-8"/>
  26. </bean>
  27. <!-- 配置多数据源映射关系 -->
  28. <bean id="dataSource" class="com.datasource.test.util.database.DynamicDataSource">
  29. <property name="targetDataSources">
  30. <map key-type="java.lang.String">
  31. <entry key="dataSource1" value-ref="dataSource1"></entry>
  32. <entry key="dataSource2" value-ref="dataSource2"></entry>
  33. <entry key="dataSource3" value-ref="dataSource3"></entry>
  34. </map>
  35. </property>
  36. <!-- 默认目标数据源为你主库数据源 -->
  37. <property name="defaultTargetDataSource" ref="dataSource1"/>
  38. </bean>
  39. <!-- JdbcTemplate使用动态数据源的配置 -->
  40. <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
  41. <property name="dataSource">
  42. <ref bean="dynamicDataSource" />
  43. </property>
  44. </bean>

接着我们还是接着上面的aop在一定规则下的配置

  1. <aop:config expose-proxy="true">
  2. <aop:pointcut id="txPointcut" expression="execution(* com.jwdstef..service.impl..*.*(..))" />
  3. <aop:aspect ref="readWriteInterceptor" order="1">
  4. <aop:around pointcut-ref="txPointcut" method="readOrWriteDB"/>
  5. </aop:aspect>
  6. </aop:config>
  7.  
  8. <bean id="readWriteInterceptor" class="com.test.ReadWriteInterceptor">
  9. <property name="readMethodList">
  10. <list>
  11. <value>query*</value>
  12. <value>use*</value>
  13. <value>get*</value>
  14. <value>count*</value>
  15. <value>find*</value>
  16. <value>list*</value>
  17. <value>search*</value>
  18. </list>
  19. </property>
  20.  
  21. <property name="writeMethodList">
  22. <list>
  23. <value>save*</value>
  24. <value>add*</value>
  25. <value>create*</value>
  26. <value>insert*</value>
  27. <value>update*</value>
  28. <value>merge*</value>
  29. <value>del*</value>
  30. <value>remove*</value>
  31. <value>put*</value>
  32. <value>write*</value>
  33. </list>
  34. </property>
  35. </bean>

配置的切面类是ReadWriteInterceptor。这样当Mapper接口的方法被调用时,会先调用这个切面类的readOrWriteDB方法。在这里需要注意<aop:aspect>中的order="1" 配置,主要是为了解决切面于切面之间的优先级问题,因为整个系统中不太可能只有一个切面类。

  1. public class ReadWriteInterceptor {
  2. private static final String DB_SERVICE = "dbService";
  3. private List<String> readMethodList = new ArrayList<String>();
  4. private List<String> writeMethodList = new ArrayList<String>();
  5. public Object readOrWriteDB(ProceedingJoinPoint pjp) throws Throwable {
  6. String methodName = pjp.getSignature().getName();
  7. if (isChooseReadDB(methodName)) {
  8. //选择slave数据源
  9. DataSourceHolder.setDataSource("data1");
  10. } else if (isChooseWriteDB(methodName)) {
  11. //选择master数据源
  12. DataSourceHolder.setDataSource("data2");
  13. } else {
  14. //选择master数据源
  15. DataSourceHolder.setDataSource("data1");
  16. }
  17. return pjp.proceed();
  18. }
  19.  
  20. private boolean isChooseWriteDB(String methodName) {
  21. for (String mappedName : this.writeMethodList) {
  22. if (isMatch(methodName, mappedName)) {
  23. return true;
  24. }
  25. }
  26. return false;
  27. }
  28.  
  29. private boolean isChooseReadDB(String methodName) {
  30. for (String mappedName : this.readMethodList) {
  31. if (isMatch(methodName, mappedName)) {
  32. return true;
  33. }
  34. }
  35. return false;
  36. }
  37.  
  38. private boolean isMatch(String methodName, String mappedName) {
  39. return PatternMatchUtils.simpleMatch(mappedName, methodName);
  40. }
  41.  
  42. public List<String> getReadMethodList() {
  43. return readMethodList;
  44. }
  45.  
  46. public void setReadMethodList(List<String> readMethodList) {
  47. this.readMethodList = readMethodList;
  48. }
  49.  
  50. public List<String> getWriteMethodList() {
  51. return writeMethodList;
  52. }
  53.  
  54. public void setWriteMethodList(List<String> writeMethodList) {
  55. this.writeMethodList = writeMethodList;
  56. }

一般来说,是一主多从,即一个master库,多个slave库的,所以还得解决多个slave库之间负载均衡、故障转移以及失败重连接等问题。

1、负载均衡问题,slave不多,系统并发读不高的话,直接使用随机数访问也是可以的。就是根据slave的台数,然后产生随机数,随机的访问slave。

2、故障转移,如果发现connection获取不到了,则把它从slave列表中移除,等其回复后,再加入到slave列表中

3、失败重连,第一次连接失败后,可以多尝试几次,如尝试10次。

spring读写分离(配置多数据源)[marked]的更多相关文章

  1. Spring配置动态数据源-读写分离和多数据源

    在现在互联网系统中,随着用户量的增长,单数据源通常无法满足系统的负载要求.因此为了解决用户量增长带来的压力,在数据库层面会采用读写分离技术和数据库拆分等技术.读写分离就是就是一个Master数据库,多 ...

  2. MySQL主从同步、读写分离配置步骤、问题解决笔记

    MySQL主从同步.读写分离配置步骤.问题解决笔记 根据要求配置MySQL主从备份.读写分离,结合网上的文档,对搭建的步骤和出现的问题以及解决的过程做了如下笔记:       现在使用的两台服务器已经 ...

  3. MySQL主从及读写分离配置

    <<MySQL主从又叫做Replication.AB复制.简单讲就是A和B两台机器做主从后,在A上写数据,B也会跟着写数据,两者数据实时同步>> MySQL主从是基于binlo ...

  4. MySQL5.6 Replication主从复制(读写分离) 配置完整版

    MySQL5.6 Replication主从复制(读写分离) 配置完整版 MySQL5.6主从复制(读写分离)教程 1.MySQL5.6开始主从复制有两种方式: 基于日志(binlog): 基于GTI ...

  5. Mysql一主多从和读写分离配置简记

    近期开发的系统中使用MySQL作为数据库,由于数据涉及到Money,所以不得不慎重.同时,用户对最大访问量也提出了要求.为了避免Mysql成为性能瓶颈并具备很好的容错能力,特此实现主从热备和读写分离. ...

  6. yii2的数据库读写分离配置

    简介 数据库读写分离是在网站遇到性能瓶颈的时候最先考虑优化的步骤,那么yii2是如何做数据库读写分离的呢?本节教程来给大家普及一下yii2的数据库读写分离配置. 两个服务器的数据同步是读写分离的前提条 ...

  7. Mycat入门配置_读写分离配置

    1.Mycat的分片 两台数据库服务器: 192.168.80.11 192.168.80.4 操作系统版本环境:centos6.5 数据库版本:5.6 mycat版本:1.4 release 数据库 ...

  8. mysql读写分离配置(整理)

    mysql读写分离配置 环境:centos7.2 mysql5.7 场景描述: 数据库Master主服务器:192.168.206.100 数据库Slave从服务器:192.168.206.200 M ...

  9. MySQL+MyCat分库分表 读写分离配置

    一. MySQL+MyCat分库分表 1 MyCat简介 java编写的数据库中间件 Mycat运行环境需要JDK. Mycat是中间件.运行在代码应用和MySQL数据库之间的应用. 前身 : cor ...

随机推荐

  1. [原]TCP/UDP使用细节备忘

    body { font-family: "Microsoft YaHei UI","Microsoft YaHei",SimSun,"Segoe UI ...

  2. 利用WiFi钓鱼法追邻居漂亮妹纸

    假设,你的邻居是一个妹纸.漂亮单身,你,技术狗,家穷人丑,集体户口.像借酱油这种老套搭讪方式的成功率对你来说实在很低. 你要做的是了解她,然后接近她.通过搜集更多的情报,为创造机会提供帮助. 初级情报 ...

  3. JavaScript之Cookie讲解

    什么是 Cookie “cookie 是存储于访问者的计算机中的变量.每当同一台计算机通过浏览器请求某个页面时,就会发送这个 cookie.你可以使用 JavaScript 来创建和取回 cookie ...

  4. SQLite中的日期基础

    SQLite包含了如下时间/日期函数: datetime().......................产生日期和时间 date()...........................产生日期 t ...

  5. [译]如何使用 Docker 组件开发 Django 项目?

    原文地址:Django Development With Docker Compose and Machine 以下为译文 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包 ...

  6. 【QT】找茬外挂制作

    找茬外挂制作 找茬游戏大家肯定都很熟悉吧,两张类似的图片,找里面的不同.在下眼神不大好,经常瞪图片半天也找不到区别.于是乎决定做个辅助工具来解放一下自己的双眼. 一.使用工具 Qt:主要是用来做界面的 ...

  7. POJ 1491

    #include<iostream> #include<cmath> #include<iomanip> #define MAXN 50 using namespa ...

  8. OneApm,NewRelic

    https://newrelic.com/ http://www.csdn.net/article/2013-03-25/2814631-new-relic-mobile-app-real-time- ...

  9. C Primer Plus之存储类、链接和内存管理

    存储时期即生存周期——变量在内存中保留的时间 变量的作用域和链接一起表明程序的哪些部分可以通过变量名来使用该变量. 注意:生存期和作用域是两个不同的概念. 作用域    作用域描述了程序中可以访问一个 ...

  10. lintcode: 生成括号

    生成括号 给定 n 对括号,请写一个函数以将其生成新的括号组合,并返回所有组合结果. 样例 给定 n = 3, 可生成的组合如下: "((()))", "(()())&q ...