背景

最近项目中为了提高数据库读写速度,想要横向扩展Oracle数据库,一个Master,多个Slave。master可以读写数据,Slave只能读数据。这就是多数据源问题了。怎么利用Spring解决这个问题呢?

测试代码

1. pom.xml

    <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.2</version><relativePath /></parent><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><scope>test</scope></dependency><dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency></dependencies>

2. 定义两个数据源名字

public enum ClientDatabase {CLIENT_A, CLIENT_B
}

3、利用Threadlocal 数据源的名字绑定到当前执行的线程上

public class ClientDatabaseContextHolder {private static ThreadLocal<ClientDatabase> CONTEXT= new ThreadLocal<>();public static void set(ClientDatabase clientDatabase) {Assert.notNull(clientDatabase, "clientDatabase cannot be null");CONTEXT.set(clientDatabase);}public static ClientDatabase getClientDatabase() {return CONTEXT.get();}public static void clear() {CONTEXT.remove();}
}

4. 继承AbstractRoutingDataSource ,Spring会在取数据源之前调用determineCurrentLookupKey方法取数据源的名字,通过数据源的名字查找数据源。这样就可以实现动态更改数据源了。readonly事务的时候,就可以切换到读的那个数据库了。

public class ClientDataSourceRouter extends AbstractRoutingDataSource {private static final Logger log = LoggerFactory.getLogger(ClientDataSourceRouter.class);@Overrideprotected Object determineCurrentLookupKey() {ClientDatabase clientDatabase = ClientDatabaseContextHolder.getClientDatabase();if (clientDatabase != null) { //有指定切换数据源切换的时候,才给输出日志 并且也只给输出成debug级别的 否则日志太多了log.info("线程[{}],此时切换到的数据源为:{}", Thread.currentThread().getId(), clientDatabase);}return clientDatabase;}
}

5. 定义两个数据源,不同的H2数据库。

@Configuration
public class RoutingTestConfiguration {@Autowiredprivate ClientADetails clientADetails;@Autowiredprivate ClientBDetails clientBDetails;@Beanpublic DataSource clientDatasource() {Map<Object, Object> targetDataSources = new HashMap<>();DataSource clientADatasource = clientADatasource();DataSource clientBDatasource = clientBDatasource();targetDataSources.put(ClientDatabase.CLIENT_A, clientADatasource);targetDataSources.put(ClientDatabase.CLIENT_B, clientBDatasource);ClientDataSourceRouter clientRoutingDatasource = new ClientDataSourceRouter();clientRoutingDatasource.setTargetDataSources(targetDataSources);clientRoutingDatasource.setDefaultTargetDataSource(clientADatasource);return clientRoutingDatasource;}private DataSource clientADatasource() {EmbeddedDatabaseBuilder dbBuilder = new EmbeddedDatabaseBuilder();return dbBuilder.setType(EmbeddedDatabaseType.H2).setName(clientADetails.getName()).addScript(clientADetails.getScript()).build();}private DataSource clientBDatasource() {EmbeddedDatabaseBuilder dbBuilder = new EmbeddedDatabaseBuilder();return dbBuilder.setType(EmbeddedDatabaseType.H2).setName(clientBDetails.getName()).addScript(clientBDetails.getScript()).build();}
}

6.  创建两个类取两个数据的名字等等。

@Component
@ConfigurationProperties(prefix = "client-a.datasource")
public class ClientBDetails {private String name = "CLIENT_B";private String script = "SOME_SCRIPT.sql";// Getters & Setters
}@Component
@ConfigurationProperties(prefix = "client-a.datasource")
public class ClientADetails {private String name = "CLIENT_A";private String script = "SOME_SCRIPT.sql";// Getters & Setters
}

7. application.properties

#database details for CLIENT_A
client-a.datasource.name=CLIENT_A
client-a.datasource.script=SOME_SCRIPT.sql#database details for CLIENT_B
client-b.datasource.name=CLIENT_B
client-b.datasource.script=SOME_SCRIPT.sql

8.  测试类

@SpringJUnitConfig
@ContextConfiguration(classes = {RoutingTestConfiguration.class, ClientBDetails.class, ClientADetails.class})
public class TestSpringBean {@Autowiredprivate DataSource dataSource;@Testpublic void test1() throws SQLException {System.out.println(dataSource.getConnection());ClientDatabaseContextHolder.set(ClientDatabase.CLIENT_B);System.out.println(dataSource.getConnection());}
}

执行结果

conn2: url=jdbc:h2:mem:CLIENT_A user=SA
19:29:04.576 [main] INFO ClientDataSourceRouter - 线程[1],此时切换到的数据源为:CLIENT_B
[jdbc:h2:mem:CLIENT_B;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false]
conn3: url=jdbc:h2:mem:CLIENT_B user=SA

利用Spring的AbstractRoutingDataSource解决多数据源的读写分离问题相关推荐

  1. 简单好用!利用Spring AOP技术10分钟实现一个读写分离方案

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达今日推荐:2020年7月程序员工资统计,平均14357元,又跌了,扎心个人原创100W+访问量博客:点击前往,查看更多 作者 ...

  2. Spring Boot 多数据源(读写分离)入门

    转载自  芋道 Spring Boot 多数据源(读写分离)入门 1. 概述 在项目中,我们可能会碰到需要多数据源的场景.例如说: 读写分离:数据库主节点压力比较大,需要增加从节点提供读操作,以减少压 ...

  3. Java 阿里巴巴数据源_阿里P7教你如何使用 Spring 配置动态数据源实现读写分离

    最近搭建的一个项目需要实现数据源的读写分离,在这里将代码进行分享,以供参考. 关键词:DataSource .AbstractRoutingDataSource.AOP 首先是配置数据源 //配置省略 ...

  4. MyBatis多数据源配置(读写分离)

    MyBatis多数据源配置(读写分离) 首先说明,本文的配置使用的最直接的方式,实际用起来可能会很麻烦. 实际应用中可能存在多种结合的情况,你可以理解本文的含义,不要死板的使用. 多数据源的可能情况 ...

  5. jpa 自定义sql if_SpringBoot整合JPA实现多数据源及读写分离

    SpringBoot整合JPA实现多数据源及读写分离 项目地址:https://github.com/baojingyu/spring-boot-jpa-dynamic-datasource 本项目使 ...

  6. 2 数据源配置_论多数据源(读写分离)的实现方案

    好的,作为一个合格的bug生产者,我们直接进入主题,多数据源和读写分离实现方案. 首先多数据源和读写分离什么时候我们才需要呢? 多数据源:一个单体项目过于复杂,需要操作多个业务库的时候,就需要多数据源 ...

  7. 使用spring aop实现业务层mysql 读写分离

    2019独角兽企业重金招聘Python工程师标准>>> 1.使用spring aop 拦截机制现数据源的动态选取. import java.lang.annotation.Eleme ...

  8. Spring 下,关于动态数据源的事务问题的探讨

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 作者:青石路 cnblogs.com/youzhibing ...

  9. 分布式数据层中间件详解:如何实现分库分表+动态数据源+读写分离

    分布式数据层中间件: 1.简介: 分布式数据访问层中间件,旨在为供一个通用数据访问层服务,支持MySQL动态数据源.读写分离.分布式唯一主键生成器.分库分表.动态化配置等功能,并且支持从客户端角度对数 ...

最新文章

  1. React项目 --ES6 语法中的class (9)
  2. [2019HDU多校第一场][HDU 6590][M. Code]
  3. 再读王永庆卖米的故事
  4. 【Java】常见的异常和Throwable类
  5. 多线程爬虫191023
  6. 【MVC5】对MySql数据库使用EntityFramework
  7. lnmp升级PHP环境
  8. visio业务流程图教学_用visio软件怎样画数据流程图和业务流程图?
  9. 腾讯QQ之下载的安装包在哪里
  10. 通过关键词采集百度网址脚本
  11. 全球顶尖的程序化交易模型
  12. 微积分review 极限,迫敛性,极限四则运算,自然常数来历
  13. 精读-软件测试的艺术之调试,极限测试和因特尔应用系统的测试
  14. Adobe Acrobat 虚拟打印机安装方法
  15. 2023年4月中国数据库排行榜:达梦厚积薄发夺探花,亚信、星环勇毅笃行有突破
  16. 如何写网络舆情数据分析报告的技巧及注意事项详解
  17. 【程序】Marvell 88W8801 WiFi模块连接路由器,并使用lwip2.0.3建立http服务器(20180807版)
  18. SVA 断言 note
  19. 深圳内推 | 华为诺亚方舟实验室招聘计算机视觉算法研究员/实习生
  20. Carthage与CocoaPods的区别和使用步骤

热门文章

  1. 什么样的前端框架才是一个好框架
  2. LFS-构建自己的linux
  3. 【原】unity3D ios 退出保存数据
  4. DataGuard常用命令及DG主备库开关顺序
  5. idea中git分支、合并与使用
  6. springboot 远程调用shell脚本,环境为windows
  7. 函数式编程 -- 测试题集
  8. windows git密码 删除
  9. 【Java】实现矩阵的转置
  10. 【Python】文本进度条