本文节选自《Spring 5核心原理》
阅读本文之前 , 请先阅读以下内容:
30个类手写Spring核心原理之自定义ORM(上)(6)
30个类手写Spring核心原理之自定义ORM(下)(7)
4动态数据源切换的底层原理这里简单介绍一下AbstractRoutingDataSource的基本原理 。实现数据源切换的功能就是自定义一个类扩展AbstractRoutingDataSource抽象类 , 其实相当于数据源的路由中介 , 可以实现在项目运行时根据相应key值切换到对应的DataSource上 。先看看AbstractRoutingDataSource类的源码:
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {/*只列出部分代码*/@Nullableprivate Map<Object, Object> targetDataSources;@Nullableprivate Object defaultTargetDataSource;private boolean lenientFallback = true;private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();@Nullableprivate Map<Object, DataSource> resolvedDataSources;@Nullableprivate 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;}}@Nullableprotected abstract Object determineCurrentLookupKey();}可以看出 , AbstractRoutingDataSource类继承了AbstractDataSource类 , 并实现了InitializingBean 。AbstractRoutingDataSource类的getConnection()方法调用了determineTargetDataSource()的该方法 。这里重点看determineTargetDataSource()方法的代码 , 它使用了determineCurrentLookupKey()方法 , 它是AbstractRoutingDataSource类的抽象方法 , 也是实现数据源切换扩展的方法 。该方法的返回值就是项目中所要用的DataSource的key值 , 得到该key值后就可以在resolvedDataSource中取出对应的DataSource , 如果找不到key对应的DataSource就使用默认的数据源 。
自定义类扩展AbstractRoutingDataSource类时要重写determineCurrentLookupKey()方法来实现数据源切换 。
4.1DynamicDataSourceDynamicDataSource类封装自定义数据源 , 继承原生Spring的AbstractRoutingDataSource类的数据源动态路由器 。
package javax.core.common.jdbc.datasource;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;/*** 动态数据源*/public class DynamicDataSource extends AbstractRoutingDataSource {private DynamicDataSourceEntry dataSourceEntry;@Overrideprotected Object determineCurrentLookupKey() {return this.dataSourceEntry.get();}public void setDataSourceEntry(DynamicDataSourceEntry dataSourceEntry) {this.dataSourceEntry = dataSourceEntry;}public DynamicDataSourceEntry getDataSourceEntry(){return this.dataSourceEntry;}}4.2DynamicDataSourceEntryDynamicDataSourceEntry类实现对数据源的操作功能 , 代码如下:
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")@Datapublic class Member implements Serializable {@Id private Long id;private String name;private String addr;private Integer age;@Overridepublic 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")@Datapublic class Order implements Serializable {private Long id;@Column(name="mid")private Long memberId;private String detail;private Long createTime;private String createTimeFmt;@Overridepublic 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;@Repositorypublic class MemberDao extends BaseDaoSupport<Member,Long> {@Overrideprotected 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;@Repositorypublic 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;@Overrideprotected 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.Driverdb2018.mysql.jdbc.url=jdbc:mysql://127.0.0.1:3306/gp-vip-spring-db-2018?characterEncoding=UTF-8&rewriteBatchedStatements=truedb2018.mysql.jdbc.username=rootdb2018.mysql.jdbc.password=123456db2019.mysql.jdbc.driverClassName=com.mysql.jdbc.Driverdb2019.mysql.jdbc.url=jdbc:mysql://127.0.0.1:3306/gp-vip-spring-db-2019?characterEncoding=UTF-8&rewriteBatchedStatements=truedb2019.mysql.jdbc.username=rootdb2019.mysql.jdbc.password=123456#alibaba druid configdbPool.initialSize=1dbPool.minIdle=1dbPool.maxActive=200dbPool.maxWait=60000dbPool.timeBetweenEvictionRunsMillis=60000dbPool.minEvictableIdleTimeMillis=300000dbPool.validationQuery=SELECT 'x' dbPool.testWhileIdle=truedbPool.testOnBorrow=falsedbPool.testOnReturn=falsedbPool.poolPreparedStatements=falsedbPool.maxPoolPreparedStatementPerConnectionSize=20dbPool.filters=stat,log4j,wall5.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://tazarkount.com/read/${dbPool.initialSize}" /><property name="minIdle" value="https://tazarkount.com/read/${dbPool.minIdle}" /><property name="maxActive" value="https://tazarkount.com/read/${dbPool.maxActive}" /><property name="maxWait" value="https://tazarkount.com/read/${dbPool.maxWait}" /><property name="timeBetweenEvictionRunsMillis" value="https://tazarkount.com/read/${dbPool.timeBetweenEvictionRunsMillis}" /><property name="minEvictableIdleTimeMillis" value="https://tazarkount.com/read/${dbPool.minEvictableIdleTimeMillis}" /><property name="validationQuery" value="https://tazarkount.com/read/${dbPool.validationQuery}" /><property name="testWhileIdle" value="https://tazarkount.com/read/${dbPool.testWhileIdle}" /><property name="testOnBorrow" value="https://tazarkount.com/read/${dbPool.testOnBorrow}" /><property name="testOnReturn" value="https://tazarkount.com/read/${dbPool.testOnReturn}" /><property name="poolPreparedStatements" value="https://tazarkount.com/read/${dbPool.poolPreparedStatements}" /><property name="maxPoolPreparedStatementPerConnectionSize" value="https://tazarkount.com/read/${dbPool.maxPoolPreparedStatementPerConnectionSize}" /><property name="filters" value="https://tazarkount.com/read/${dbPool.filters}" /></bean><bean id="dataSource" parent="datasourcePool"><property name="driverClassName" value="https://tazarkount.com/read/${db2019.mysql.jdbc.driverClassName}" /><property name="url" value="https://tazarkount.com/read/${db2019.mysql.jdbc.url}" /><property name="username" value="https://tazarkount.com/read/${db2019.mysql.jdbc.username}" /><property name="password" value="https://tazarkount.com/read/${db2019.mysql.jdbc.password}" /></bean><bean id="dataSource2018" parent="datasourcePool"><property name="driverClassName" value="https://tazarkount.com/read/${db2018.mysql.jdbc.driverClassName}" /><property name="url" value="https://tazarkount.com/read/${db2018.mysql.jdbc.url}" /><property name="username" value="https://tazarkount.com/read/${db2018.mysql.jdbc.username}" /><property name="password" value="https://tazarkount.com/read/${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;@Testpublic void testSelectAllForMember(){try {List<Member> result = memberDao.selectAll();System.out.println(Arrays.toString(result.toArray()));} catch (Exception e) {e.printStackTrace();}}@Test@Ignorepublic 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// @Ignorepublic 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弹架构”原创 , 转载请注明出处 。技术在于分享 , 我分享我快乐!
如果本文对您有帮助 , 欢迎关注和点赞;如果您有任何建议也可留言评论或私信 , 您的支持是我坚持创作的动力 。
【30个类手写spring 8 30个类手写Spring核心原理之动态数据源切换】原创不易 , 坚持很酷 , 都看到这里了 , 小伙伴记得点赞、收藏、在看 , 一键三连加关注!如果你觉得内容太干 , 可以分享转发给朋友滋润滋润!
- 春季老年人吃什么养肝?土豆、米饭换着吃
- 三八妇女节节日祝福分享 三八妇女节节日语录
- 老人谨慎!选好你的“第三只脚”
- 校方进行了深刻的反思 青岛一大学生坠亡校方整改校规
- 脸皮厚的人长寿!有这特征的老人最长寿
- 长寿秘诀:记住这10大妙招 100%增寿
- 春季老年人心血管病高发 3条保命要诀
- 眼睛花不花要看四十八 老年人怎样延缓老花眼
- 香槟然能防治老年痴呆症? 一天三杯它人到90不痴呆
- 老人手抖的原因 为什么老人手会抖
