本文节选自《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")@Datapublic 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")@Datapublic 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;@Repositorypublic 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;@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;   @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.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,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弹架构 』可获取更多技术干货!

原创不易,保持很酷,都看到这里了,小伙伴记得点赞、珍藏、在看,一键三连加关注!如果你感觉内容太干,能够分享转发给敌人滋润滋润!