【#yyds干货盘点# 30个类手写Spring核心原理之动态数据源切换】沉舟侧畔千帆进,病树前头万木春。这篇文章主要讲述#yyds干货盘点# 30个类手写Spring核心原理之动态数据源切换相关的知识,希望能为你提供帮助。
阅读本文之前,请先阅读以下内容:
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.1DynamicDataSource
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.2DynamicDataSourceEntry
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() throwsException
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=123456db2018.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=123456db2019.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="https://www.songbingjia.com/android/$dbPool.initialSize" />
<
property name="minIdle" value="https://www.songbingjia.com/android/$dbPool.minIdle" />
<
property name="maxActive" value="https://www.songbingjia.com/android/$dbPool.maxActive" />
<
property name="maxWait" value="https://www.songbingjia.com/android/$dbPool.maxWait" />
<
property name="timeBetweenEvictionRunsMillis" value="https://www.songbingjia.com/android/$dbPool.timeBetweenEvictionRunsMillis" />
<
property name="minEvictableIdleTimeMillis" value="https://www.songbingjia.com/android/$dbPool.minEvictableIdleTimeMillis" />
<
property name="validationQuery" value="https://www.songbingjia.com/android/$dbPool.validationQuery" />
<
property name="testWhileIdle" value="https://www.songbingjia.com/android/$dbPool.testWhileIdle" />
<
property name="testOnBorrow" value="https://www.songbingjia.com/android/$dbPool.testOnBorrow" />
<
property name="testOnReturn" value="https://www.songbingjia.com/android/$dbPool.testOnReturn" />
<
property name="poolPreparedStatements" value="https://www.songbingjia.com/android/$dbPool.poolPreparedStatements" />
<
property name="maxPoolPreparedStatementPerConnectionSize" value="https://www.songbingjia.com/android/$dbPool.maxPoolPreparedStatementPerConnectionSize" />
<
property name="filters" value="https://www.songbingjia.com/android/$dbPool.filters" />
<
/bean>
<
bean id="dataSource" parent="datasourcePool">
<
property name="driverClassName" value="https://www.songbingjia.com/android/$db2019.mysql.jdbc.driverClassName" />
<
property name="url" value="https://www.songbingjia.com/android/$db2019.mysql.jdbc.url" />
<
property name="username" value="https://www.songbingjia.com/android/$db2019.mysql.jdbc.username" />
<
property name="password" value="https://www.songbingjia.com/android/$db2019.mysql.jdbc.password" />
<
/bean>
<
bean id="dataSource2018" parent="datasourcePool">
<
property name="driverClassName" value="https://www.songbingjia.com/android/$db2018.mysql.jdbc.driverClassName" />
<
property name="url" value="https://www.songbingjia.com/android/$db2018.mysql.jdbc.url" />
<
property name="username" value="https://www.songbingjia.com/android/$db2018.mysql.jdbc.username" />
<
property name="password" value="https://www.songbingjia.com/android/$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为空,其他条件不能为空,都为空不予执行
//ReturnIdinsert(T entity) 只要entity不等于null
//Int update(T entity) entity中的ID不能为空,如果ID为空,其他条件不能为空,都为空不予执行
然后在此基础上进行扩展,基于Spring JDBC封装一套,基于Redis封装一套,基于MongoDB封装一套,基于ElasticSearch封装一套,基于Hive封装一套,基于HBase封装一套。本文完整地演示了自研ORM框架的原理,以及数据源动态切换的基本原理,并且了解了Spring JdbcTemplate的API应用。希望通过本章的学习,“小伙伴们”在日常工作中能够有更好的解决问题的思路,提高工作效率。
关注微信公众号『 Tom弹架构 』回复“Spring”可获取完整源码。
原创不易,坚持很酷,都看到这里了,小伙伴记得点赞、收藏、在看,一键三连加关注!如果你觉得内容太干,可以分享转发给朋友滋润滋润!
推荐阅读
- Spring | Spring5学习笔记#yyds干货盘点#
- #私藏项目实操分享# 你了解shiro吗(手把手教你集成shiro)
- Veeam Backup for Red Hat Virtualization
- 快速入门vue,含实战案例,“建议收藏”,用到省的百度了#yyds干货盘点#
- IDEA对Docker容器进行打包构建的两种方式
- 「自我检验」熬夜总结50个Vue知识点,全都会你就是神!!!
- MongoDB学习笔记-使用 MongoDB 进行 CRUD 操作(上)
- #yyds干货盘点#mybatis-plus学习与实践代码生成器整合swagger2生成CRUD接口
- #私藏项目实操分享#Alibaba中间件技术系列「RocketMQ技术专题」RocketMQ消息发送的全部流程和落盘原理分析