30个类手写Spring核心原理之动态数据源切换(8)
本文节选自《Spring 5核心原理》
阅读本文之前,请先阅读以下内容:
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弹架构 』回复“Spring”可获取完整源码。
本文为“Tom弹架构”原创,转载请注明出处。技术在于分享,我分享我快乐!
如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力。关注微信公众号『 Tom弹架构 』可获取更多技术干货!
原创不易,坚持很酷,都看到这里了,小伙伴记得点赞、收藏、在看,一键三连加关注!如果你觉得内容太干,可以分享转发给朋友滋润滋润!

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Gradle 与 AGP 构建 API: 配置您的构建文件
欢迎阅读全新的 MAD Skills 系列 之 Gradle 及 Android Gradle plugin API 的第一篇文章。我们将在本文中了解 Android 构建系统的工作方式以及 Gradle 的基础知识。 我们将会从 Gradle 的构建阶段开始,讨论如何使用 AGP (Android Gradle Plugin) 的配置选项自定义您的构建,并讨论如何使您的构建保持高效。如果您更喜欢通过视频了解此内容,请在 此处 查看。 通过了解构建阶段的工作原理及配置 Android Gradle plugin 的配置方法,可以帮您基于项目的需求自定义构建。让我们回到 Android Studio,一起看看构建系统是如何工作的吧。 Gradle 简介 Gradle 是一个通用的自动化构建工具。当然,您可以使用 Gradle 来构建 Android 项目,但实际上您可以使用 Gradle 来构建任何类型的软件。 Gradle 支持单一或多项目构建。如果要将项目配置为使用 Gradle,您需要在项目文件夹中添加 build.gradle 文件。 在多项目层级结构中,根项目中会包含一个 se...
- 下一篇
🏆【Alibaba中间件技术系列】「RocketMQ技术专题」RocketMQ消息发送的全部流程和落盘原理分析
前言介绍 RocketMQ目前在国内应该是比较流行的MQ 了,目前本人也在公司的项目中进行使用和研究,借着这个机会,分析一下RocketMQ 发送一条消息到存储一条消息的过程,这样会对以后大家分析和研究RocketMQ相关的问题有一定的帮助。 技术范围 分析的总体技术范围发送到存储,本文的主要目的是主要是为了认识一条消息并分析被发出且被存储的,代码中,关于 MQ 文件系统的优化,设计等。 现在出发 来自官方源码example的一段发送代码: DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName"); producer.start(); Message msg = new Message("TopicTest", "TagA", "OrderID188", "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); SendResult sendResult = producer.send(msg); System.out.printf("%s%n"...
相关文章
文章评论
共有0条评论来说两句吧...