#yyds干货盘点# 30个类手写Spring核心原理之动态数据源切换

【#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”可获取完整源码。
原创不易,坚持很酷,都看到这里了,小伙伴记得点赞、收藏、在看,一键三连加关注!如果你觉得内容太干,可以分享转发给朋友滋润滋润!

    推荐阅读