30个类手写Spring核心原理之动态数据源切换(8)
本文节选自《Spring 5核心原理》
阅读本文之前,请先阅读以下内容:
30个类手写Spring核心原理之自定义ORM(上)(6)
30个类手写Spring核心原理之自定义ORM(下)(7)
4 动态数据源切换的底层原理
这里简单介绍一下AbstractRoutingDataSource的基本原理。实现数据源切换的功能就是自定义一个类扩展AbstractRoutingDataSource抽象类,其实相当于数据源的路由中介,可以实现在项目运行时根据相应key值切换到对应的DataSource上。先看看AbstractRoutingDataSource类的源码:
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
/*只列出部分代码*/
@Nullable
private Map<Object, Object> targetDataSources;
@Nullable
private Object defaultTargetDataSource;
private boolean lenientFallback = true;
private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
@Nullable
private Map<Object, DataSource> resolvedDataSources;
@Nullable
private DataSource resolvedDefaultDataSource;
...
public Connection getConnection() throws SQLException {
return this.determineTargetDataSource().getConnection();
}
public Connection getConnection(String username, String password) throws SQLException {
return this.determineTargetDataSource().getConnection(username, password);
}
...
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = this.determineCurrentLookupKey();
DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
if(dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if(dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
} else {
return dataSource;
}
}
@Nullable
protected abstract Object determineCurrentLookupKey();
}
可以看出,AbstractRoutingDataSource类继承了AbstractDataSource类,并实现了InitializingBean。AbstractRoutingDataSource类的getConnection()方法调用了determineTargetDataSource()的该方法。这里重点看determineTargetDataSource()方法的代码,它使用了determineCurrentLookupKey()方法,它是AbstractRoutingDataSource类的抽象方法,也是实现数据源切换扩展的方法。该方法的返回值就是项目中所要用的DataSource的key值,得到该key值后就可以在resolvedDataSource中取出对应的DataSource,如果找不到key对应的DataSource就使用默认的数据源。
自定义类扩展AbstractRoutingDataSource类时要重写determineCurrentLookupKey()方法来实现数据源切换。
4.1 DynamicDataSource
DynamicDataSource类封装自定义数据源,继承原生Spring的AbstractRoutingDataSource类的数据源动态路由器。
package javax.core.common.jdbc.datasource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 动态数据源
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
private DynamicDataSourceEntry dataSourceEntry;
@Override
protected Object determineCurrentLookupKey() {
return this.dataSourceEntry.get();
}
public void setDataSourceEntry(DynamicDataSourceEntry dataSourceEntry) {
this.dataSourceEntry = dataSourceEntry;
}
public DynamicDataSourceEntry getDataSourceEntry(){
return this.dataSourceEntry;
}
}
4.2 DynamicDataSourceEntry
DynamicDataSourceEntry类实现对数据源的操作功能,代码如下:
package javax.core.common.jdbc.datasource;
import org.aspectj.lang.JoinPoint;
/**
* 动态切换数据源
*/
public class DynamicDataSourceEntry {
//默认数据源
public final static String DEFAULT_SOURCE = null;
private final static ThreadLocal<String> local = new ThreadLocal<String>();
/**
* 清空数据源
*/
public void clear() {
local.remove();
}
/**
* 获取当前正在使用的数据源的名字
*
* @return String
*/
public String get() {
return local.get();
}
/**
* 还原指定切面的数据源
*
* @param joinPoint
*/
public void restore(JoinPoint join) {
local.set(DEFAULT_SOURCE);
}
/**
* 还原当前切面的数据源
*/
public void restore() {
local.set(DEFAULT_SOURCE);
}
/**
* 设置已知名字的数据源
*
* @param dataSource
*/
public void set(String source) {
local.set(source);
}
/**
* 根据年份动态设置数据源
* @param year
*/
public void set(int year) {
local.set("DB_" + year);
}
}
5 运行效果演示
5.1 创建Member实体类
创建Member实体类代码如下:
package com.tom.orm.demo.entity;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="t_member")
@Data
public class Member implements Serializable {
@Id private Long id;
private String name;
private String addr;
private Integer age;
@Override
public String toString() {
return "Member{" +
"id=" + id +
", name='" + name + '\'' +
", addr='" + addr + '\'' +
", age=" + age +
'}';
}
}
5.2 创建Order实体类
创建Order实体类代码如下:
package com.tom.orm.demo.entity;
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="t_order")
@Data
public class Order implements Serializable {
private Long id;
@Column(name="mid")
private Long memberId;
private String detail;
private Long createTime;
private String createTimeFmt;
@Override
public String toString() {
return "Order{" +
"id=" + id +
", memberId=" + memberId +
", detail='" + detail + '\'' +
", createTime=" + createTime +
", createTimeFmt='" + createTimeFmt + '\'' +
'}';
}
}
5.3 创建MemberDao
创建MemberDao代码如下:
package com.tom.orm.demo.dao;
import com.tom.orm.demo.entity.Member;
import com.tom.orm.framework.BaseDaoSupport;
import com.tom.orm.framework.QueryRule;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.List;
@Repository
public class MemberDao extends BaseDaoSupport<Member,Long> {
@Override
protected String getPKColumn() {
return "id";
}
@Resource(name="dataSource")
public void setDataSource(DataSource dataSource){
super.setDataSourceReadOnly(dataSource);
super.setDataSourceWrite(dataSource);
}
public List<Member> selectAll() throws Exception{
QueryRule queryRule = QueryRule.getInstance();
queryRule.andLike("name","Tom%");
return super.select(queryRule);
}
}
5.4 创建OrderDao
创建OrderDao代码如下:
package com.tom.orm.demo.dao;
import com.tom.orm.demo.entity.Order;
import com.tom.orm.framework.BaseDaoSupport;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import javax.core.common.jdbc.datasource.DynamicDataSource;
import javax.sql.DataSource;
import java.text.SimpleDateFormat;
import java.util.Date;
@Repository
public class OrderDao extends BaseDaoSupport<Order, Long> {
private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");
private SimpleDateFormat fullDataFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private DynamicDataSource dataSource;
@Override
protected String getPKColumn() {return "id";}
@Resource(name="dynamicDataSource")
public void setDataSource(DataSource dataSource) {
this.dataSource = (DynamicDataSource)dataSource;
this.setDataSourceReadOnly(dataSource);
this.setDataSourceWrite(dataSource);
}
/**
* @throws Exception
*
*/
public boolean insertOne(Order order) throws Exception{
//约定优于配置
Date date = null;
if(order.getCreateTime() == null){
date = new Date();
order.setCreateTime(date.getTime());
}else {
date = new Date(order.getCreateTime());
}
Integer dbRouter = Integer.valueOf(yearFormat.format(date));
System.out.println("自动分配到【DB_" + dbRouter + "】数据源");
this.dataSource.getDataSourceEntry().set(dbRouter);
order.setCreateTimeFmt(fullDataFormat.format(date));
Long orderId = super.insertAndReturnId(order);
order.setId(orderId);
return orderId > 0;
}
}
5.5 修改db.properties文件
修改db.properties文件代码如下:
#sysbase database mysql config
#mysql.jdbc.driverClassName=com.mysql.jdbc.Driver
#mysql.jdbc.url=jdbc:mysql://127.0.0.1:3306/gp-vip-spring-db-demo?characterEncoding=UTF-8&rewriteBatchedStatements=true
#mysql.jdbc.username=root
#mysql.jdbc.password=123456
db2018.mysql.jdbc.driverClassName=com.mysql.jdbc.Driver
db2018.mysql.jdbc.url=jdbc:mysql://127.0.0.1:3306/gp-vip-spring-db-2018?characterEncoding=UTF-8&rewriteBatchedStatements=true
db2018.mysql.jdbc.username=root
db2018.mysql.jdbc.password=123456
db2019.mysql.jdbc.driverClassName=com.mysql.jdbc.Driver
db2019.mysql.jdbc.url=jdbc:mysql://127.0.0.1:3306/gp-vip-spring-db-2019?characterEncoding=UTF-8&rewriteBatchedStatements=true
db2019.mysql.jdbc.username=root
db2019.mysql.jdbc.password=123456
#alibaba druid config
dbPool.initialSize=1
dbPool.minIdle=1
dbPool.maxActive=200
dbPool.maxWait=60000
dbPool.timeBetweenEvictionRunsMillis=60000
dbPool.minEvictableIdleTimeMillis=300000
dbPool.validationQuery=SELECT 'x'
dbPool.testWhileIdle=true
dbPool.testOnBorrow=false
dbPool.testOnReturn=false
dbPool.poolPreparedStatements=false
dbPool.maxPoolPreparedStatementPerConnectionSize=20
dbPool.filters=stat,log4j,wall
5.6 修改application-db.xml文件
修改application-db.xml文件代码如下:
<bean id="datasourcePool" abstract="true" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="initialSize" value="${dbPool.initialSize}" />
<property name="minIdle" value="${dbPool.minIdle}" />
<property name="maxActive" value="${dbPool.maxActive}" />
<property name="maxWait" value="${dbPool.maxWait}" />
<property name="timeBetweenEvictionRunsMillis" value="${dbPool.timeBetweenEvictionRunsMillis}" />
<property name="minEvictableIdleTimeMillis" value="${dbPool.minEvictableIdleTimeMillis}" />
<property name="validationQuery" value="${dbPool.validationQuery}" />
<property name="testWhileIdle" value="${dbPool.testWhileIdle}" />
<property name="testOnBorrow" value="${dbPool.testOnBorrow}" />
<property name="testOnReturn" value="${dbPool.testOnReturn}" />
<property name="poolPreparedStatements" value="${dbPool.poolPreparedStatements}" />
<property name="maxPoolPreparedStatementPerConnectionSize" value="${dbPool.maxPoolPreparedStatementPerConnectionSize}" />
<property name="filters" value="${dbPool.filters}" />
</bean>
<bean id="dataSource" parent="datasourcePool">
<property name="driverClassName" value="${db2019.mysql.jdbc.driverClassName}" />
<property name="url" value="${db2019.mysql.jdbc.url}" />
<property name="username" value="${db2019.mysql.jdbc.username}" />
<property name="password" value="${db2019.mysql.jdbc.password}" />
</bean>
<bean id="dataSource2018" parent="datasourcePool">
<property name="driverClassName" value="${db2018.mysql.jdbc.driverClassName}" />
<property name="url" value="${db2018.mysql.jdbc.url}" />
<property name="username" value="${db2018.mysql.jdbc.username}" />
<property name="password" value="${db2018.mysql.jdbc.password}" />
</bean>
<bean id="dynamicDataSourceEntry" class="javax.core.common.jdbc.datasource.DynamicDataSourceEntry" />
<bean id="dynamicDataSource" class="javax.core.common.jdbc.datasource.DynamicDataSource" >
<property name="dataSourceEntry" ref="dynamicDataSourceEntry"></property>
<property name="targetDataSources">
<map>
<entry key="DB_2019" value-ref="dataSource"></entry>
<entry key="DB_2018" value-ref="dataSource2018"></entry>
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSource" />
</bean>
5.7 编写测试用例
编写测试用例代码如下:
package com.tom.orm.test;
import com.tom.orm.demo.dao.MemberDao;
import com.tom.orm.demo.dao.OrderDao;
import com.tom.orm.demo.entity.Member;
import com.tom.orm.demo.entity.Order;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
@ContextConfiguration(locations = {"classpath:application-context.xml"})
@RunWith(SpringJUnit4ClassRunner.class)
public class OrmTest {
private SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmdd");
@Autowired private MemberDao memberDao;
@Autowired private OrderDao orderDao;
@Test
public void testSelectAllForMember(){
try {
List<Member> result = memberDao.selectAll();
System.out.println(Arrays.toString(result.toArray()));
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
@Ignore
public void testInsertMember(){
try {
for (int age = 25; age < 35; age++) {
Member member = new Member();
member.setAge(age);
member.setName("Tom");
member.setAddr("Hunan Changsha");
memberDao.insert(member);
}
}catch (Exception e){
e.printStackTrace();
}
}
@Test
// @Ignore
public void testInsertOrder(){
try {
Order order = new Order();
order.setMemberId(1L);
order.setDetail("历史订单");
Date date = sdf.parse("20180201123456");
order.setCreateTime(date.getTime());
orderDao.insertOne(order);
}catch (Exception e){
e.printStackTrace();
}
}
}
所谓ORM就是,对象关系映射,Object Relation Mapping,市面上ORM框架也非常多,比如Hibernate、Spring JDBC、MyBatis、JPA,它们都有对象关系管理的机制比如一对多、多对多、一对一关系。以上思路仅供参考,有兴趣的小伙伴可以参考本文提供的思想,约定优于配置,先制定顶层接口,参数返回值全部统一,比如:
//List<?> Page<?> select(QueryRule queryRule)
//Int delete(T entity) entity中的ID不能为空,如果ID为空,其他条件不能为空,都为空不予执行
//ReturnId insert(T entity) 只要entity不等于null
//Int update(T entity) entity中的ID不能为空,如果ID为空,其他条件不能为空,都为空不予执行
然后在此基础上进行扩展,基于Spring JDBC封装一套,基于Redis封装一套,基于MongoDB封装一套,基于ElasticSearch封装一套,基于Hive封装一套,基于HBase封装一套。本文完整地演示了自研ORM框架的原理,以及数据源动态切换的基本原理,并且了解了Spring JdbcTemplate的API应用。希望通过本章的学习,“小伙伴们”在日常工作中能够有更好的解决问题的思路,提高工作效率。
本文为“Tom弹架构”原创,转载请注明出处。技术在于分享,我分享我快乐!
如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力。
原创不易,坚持很酷,都看到这里了,小伙伴记得点赞、收藏、在看,一键三连加关注!如果你觉得内容太干,可以分享转发给朋友滋润滋润!
30个类手写Spring核心原理之动态数据源切换(8)的更多相关文章
- 30个类手写Spring核心原理之环境准备(1)
本文节选自<Spring 5核心原理> 1 IDEA集成Lombok插件 1.1 安装插件 IntelliJ IDEA是一款非常优秀的集成开发工具,功能强大,而且插件众多.Lombok是开 ...
- 30个类手写Spring核心原理之依赖注入功能(3)
本文节选自<Spring 5核心原理> 在之前的源码分析中我们已经了解到,依赖注入(DI)的入口是getBean()方法,前面的IoC手写部分基本流程已通.先在GPApplicationC ...
- 30个类手写Spring核心原理之AOP代码织入(5)
本文节选自<Spring 5核心原理> 前面我们已经完成了Spring IoC.DI.MVC三大核心模块的功能,并保证了功能可用.接下来要完成Spring的另一个核心模块-AOP,这也是最 ...
- 30个类手写Spring核心原理之自定义ORM(上)(6)
本文节选自<Spring 5核心原理> 1 实现思路概述 1.1 从ResultSet说起 说到ResultSet,有Java开发经验的"小伙伴"自然最熟悉不过了,不过 ...
- 30个类手写Spring核心原理之Ioc顶层架构设计(2)
本文节选自<Spring 5核心原理> 1 Annotation(自定义配置)模块 Annotation的代码实现我们还是沿用Mini版本的,保持不变,复制过来便可. 1.1 @GPSer ...
- 30个类手写Spring核心原理之MVC映射功能(4)
本文节选自<Spring 5核心原理> 接下来我们来完成MVC模块的功能,应该不需要再做说明.Spring MVC的入口就是从DispatcherServlet开始的,而前面的章节中已完成 ...
- 手写webpack核心原理,再也不怕面试官问我webpack原理
手写webpack核心原理 目录 手写webpack核心原理 一.核心打包原理 1.1 打包的主要流程如下 1.2 具体细节 二.基本准备工作 三.获取模块内容 四.分析模块 五.收集依赖 六.ES6 ...
- 手写Spring MVC
闲及无聊 又打开了CSDN开始看一看有什么先进的可以学习的相关帖子,这时看到了一位大神写的简历装X必备,手写Spring MVC. 我想这个东西还是有一点意思的 就拜读了一下大佬的博客 通读了一遍相关 ...
- Spring事务原理分析--手写Spring事务
一.基本概念和原理 1.Spring事务 基于AOP环绕通知和异常通知的 2.Spring事务分为编程式事务.声明事务.编程事务包括注解方式和扫包方式(xml) Spring事务底层使用编程事务(自己 ...
随机推荐
- [atAGC048F]01 Record
先将这个序列翻转,贪心找到最长的'101010--'的形式的子序列并删除,重复此过程并记这些字符串长度依次为$l_{1},l_{2},...,l_{n}$,若最终还有字符剩余则一定无解 假设$S$中元 ...
- 智能 Request 推荐,K8s 资源利用率提升 252%
作者 王孝威,FinOps 认证从业者,腾讯云容器服务产品经理,热衷于为客户提供高效的 Kubernetes 使用方式,为客户极致降本增效服务. 余宇飞,FinOps 认证从业者,腾讯云专家工程师,从 ...
- git分支切换的一些问题
关于git切换分支后该分支的修改会在另一个分支里面一起修改的问题 修改分支后导致稳定版的主分支里面的文件连带修改. 原因:切换分支前原分支没有提交,导致新建的文件或者文件夹,没有纳入版本管理,所以会被 ...
- char *p、char p[]、字符串的几个题目
总结一下遇到的关于char *p.char p[]和字符串的题目: 例一:(指针的指针) 1 void getmemory(char **p) 2 { 3 p = (char *)malloc(100 ...
- 基因组共线性分析工具MCScanX
软件简介 MCScanX工具集对MCScan算法进行了调整,用于检测共线性和同线性区域,还增加了可视化和下游分析..MCscanX有三个核心工具,以及12个下游分析工具. 软件安装 进入官网http: ...
- 出现NoClassDefFoundError,始终无法引入jar的解决
在拉取代码后,项目的部分版本与本地存在的不一定一致,所以IDEA会自动下载并引入,但是在启动时可能存在java.lang.NoClassDefFoundError这个报错 比如引入marshallin ...
- Tikz绘制形似万花尺的图片
初中时意外发现数学课本上有这么一个好玩的图 大概就是把两条相等线段A.B分为10个小段并在点上标序号,A线段1点连B线段9点,2点连8点,依次类推. 假设有这么一个框架图 按照第一张图的方式进一步绘图 ...
- 【模板】Splay(伸展树)普通平衡树(数据加强版)/洛谷P6136
题目链接 https://www.luogu.com.cn/problem/P6136 题目大意 需要写一种数据结构,来维护一些非负整数( \(int\) 范围内)的升序序列,其中需要提供以下操作: ...
- Yarn 公平调度器案例
目录 公平调度器案例 需求 配置多队列的公平调度器 1 修改yarn-site.xml文件,加入以下从参数 2 配置fair-scheduler.xml 3 分发配置文件重启yarn 4 测试提交任务 ...
- day09搭建均衡负载和搭建BBS博客系统
day09搭建均衡负载和搭建BBS博客系统 搭建BBS博客系统 本次搭建bbs用到的技术 需要用到的: 1.Nginx+Django 2.Django+MySQL 环境准备 主机 IP 身份 db01 ...