飙血推荐
  • HTML教程
  • MySQL教程
  • JavaScript基础教程
  • php入门教程
  • JavaScript正则表达式运用
  • Excel函数教程
  • UEditor使用文档
  • AngularJS教程
  • ThinkPHP5.0教程

30个类手写Spring核心原理之动态数据源切换(8)

时间:2021-12-22  作者:gupaoedu-tom  

本文节选自《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 域名rmineTargetDataSource().getConnection();
    }

    public Connection getConnection(String username, String password) throws SQLException {
        return 域名rmineTargetDataSource().getConnection(username, password);
    }

    ...

    protected DataSource determineTargetDataSource() {
        域名ull(域名lvedDataSources, "DataSource router not initialized");
        Object lookupKey = 域名rmineCurrentLookupKey();
        DataSource dataSource = (DataSource)域名(lookupKey);
        if(dataSource == null && (域名entFallback || lookupKey == null)) {
            dataSource = 域名lvedDefaultDataSource;
        }

        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 域名域名source;

import 域名.域名ractRoutingDataSource;
/** 
 * 动态数据源 
 */  
public class DynamicDataSource extends AbstractRoutingDataSource {  
   private DynamicDataSourceEntry dataSourceEntry;  
    @Override  
    protected Object determineCurrentLookupKey() {
        return 域名();  
    }  
    public void setDataSourceEntry(DynamicDataSourceEntry dataSourceEntry) {  
        域名SourceEntry = dataSourceEntry;
    }
    public DynamicDataSourceEntry getDataSourceEntry(){
          return 域名SourceEntry;
    }
}

4.2 DynamicDataSourceEntry

DynamicDataSourceEntry类实现对数据源的操作功能,代码如下:


package 域名域名source;

import 域名.JoinPoint;

/**
 * 动态切换数据源
 */
public class DynamicDataSourceEntry {
   
   //默认数据源  
    public final static String DEFAULT_SOURCE = null;  
  
    private final static ThreadLocal<String> local = new ThreadLocal<String>();  
   
    /** 
     * 清空数据源 
     */  
    public void clear() {  
        域名ve();
    }  
    
    /** 
     * 获取当前正在使用的数据源的名字
     *  
     * @return String 
     */  
    public String get() {  
         return 域名();  
    }  
  
    /** 
     * 还原指定切面的数据源 
     *  
     * @param joinPoint 
     */
    public void restore(JoinPoint join) {  
        域名(DEFAULT_SOURCE);  
    }
    
    /**
     * 还原当前切面的数据源
     */
    public void restore() {  
        域名(DEFAULT_SOURCE);
    }  
  
    /** 
     * 设置已知名字的数据源 
     *  
     * @param dataSource 
     */  
    public void set(String source) {  
        域名(source); 
    }

    /**
     * 根据年份动态设置数据源
     * @param year
     */
   public void set(int year) {
      域名("DB_" + year);
   }
}

5 运行效果演示

5.1 创建Member实体类

创建Member实体类代码如下:


package 域名.域名ty;

import 域名;

import 域名ty;
import 域名;
import 域名e;
import 域名alizable;

@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 域名.域名ty;

import 域名;

import 域名mn;
import 域名ty;
import 域名e;
import 域名alizable;

@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 域名.域名;

import 域名.域名er;
import 域名.域名DaoSupport;
import 域名.域名yRule;
import 域名域名sitory;

import 域名urce;
import 域名Source;
import 域名;

@Repository
public class MemberDao extends BaseDaoSupport<Member,Long> {
    
    @Override
    protected String getPKColumn() {
        return "id";
    }

    @Resource(name="dataSource")
    public void setDataSource(DataSource dataSource){
        域名ataSourceReadOnly(dataSource);
        域名ataSourceWrite(dataSource);
    }


    public List<Member> selectAll() throws  Exception{
        QueryRule queryRule = 域名nstance();
        域名ike("name","Tom%");
        return 域名ct(queryRule);
    }
}

5.4 创建OrderDao

创建OrderDao代码如下:


package 域名.域名;

import 域名.域名r;
import 域名.域名DaoSupport;
import 域名域名sitory;

import 域名urce;
import 域名域名域名micDataSource;
import 域名Source;
import 域名leDateFormat;
import 域名;


@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) {
      域名Source = (DynamicDataSource)dataSource;
      域名ataSourceReadOnly(dataSource);
      域名ataSourceWrite(dataSource);
   }

   /**
    * @throws Exception
    *
    */
   public boolean insertOne(Order order) throws Exception{
      //约定优于配置
      Date date = null;
      if(域名reateTime() == null){
         date = new Date();
         域名reateTime(域名ime());
      }else {
         date = new Date(域名reateTime());
      }
      Integer dbRouter = 域名eOf(域名at(date));
      域名tln("自动分配到【DB_" + dbRouter + "】数据源");
      域名ataSourceEntry().set(dbRouter);

      域名reateTimeFmt(域名at(date));

      Long orderId = 域名rtAndReturnId(order);
      域名d(orderId);
      return orderId > 0;
   }

   
}

5.5 修改域名erties文件

修改域名erties文件代码如下:


#sysbase database mysql config

#域名erClassName=域名.Driver
#域名=jdbc:mysql://127.0.0.1:3306/gp-vip-spring-db-demo?characterEncoding=UTF-8&rewriteBatchedStatements=true
#域名name=root
#域名word=123456

域名.driverClassName=域名.Driver
域名.url=jdbc:mysql://127.0.0.1:3306/gp-vip-spring-db-2018?characterEncoding=UTF-8&rewriteBatchedStatements=true
域名.username=root
域名.password=123456

域名.driverClassName=域名.Driver
域名.url=jdbc:mysql://127.0.0.1:3306/gp-vip-spring-db-2019?characterEncoding=UTF-8&rewriteBatchedStatements=true
域名.username=root
域名.password=123456

#alibaba druid config
域名ialSize=1
域名dle=1
域名ctive=200
域名ait=60000
域名BetweenEvictionRunsMillis=60000
域名victableIdleTimeMillis=300000
域名dationQuery=SELECT \'x\' 
域名WhileIdle=true
域名OnBorrow=false
域名OnReturn=false
域名PreparedStatements=false
域名oolPreparedStatementPerConnectionSize=20
域名ers=stat,log4j,wall

5.6 修改application-域名文件

修改application-域名文件代码如下:


<bean id="datasourcePool" abstract="true" class="域名域名dDataSource" init-method="init" destroy-method="close">
   <property name="initialSize" value="${域名ialSize}" />
   <property name="minIdle" value="${域名dle}" />
   <property name="maxActive" value="${域名ctive}" />
   <property name="maxWait" value="${域名ait}" />
   <property name="timeBetweenEvictionRunsMillis" value="${域名BetweenEvictionRunsMillis}" />
   <property name="minEvictableIdleTimeMillis" value="${域名victableIdleTimeMillis}" />
   <property name="validationQuery" value="${域名dationQuery}" />
   <property name="testWhileIdle" value="${域名WhileIdle}" />
   <property name="testOnBorrow" value="${域名OnBorrow}" />
   <property name="testOnReturn" value="${域名OnReturn}" />
   <property name="poolPreparedStatements" value="${域名PreparedStatements}" />
   <property name="maxPoolPreparedStatementPerConnectionSize" value="${域名oolPreparedStatementPerConnectionSize}" />
   <property name="filters" value="${域名ers}" />
</bean>

<bean id="dataSource" parent="datasourcePool">
   <property name="driverClassName" value="${域名.driverClassName}" />
   <property name="url" value="${域名.url}" />
   <property name="username" value="${域名.username}" />
   <property name="password" value="${域名.password}" />
</bean>

<bean id="dataSource2018" parent="datasourcePool">
   <property name="driverClassName" value="${域名.driverClassName}" />
   <property name="url" value="${域名.url}" />
   <property name="username" value="${域名.username}" />
   <property name="password" value="${域名.password}" />
</bean>


<bean id="dynamicDataSourceEntry"  class="域名域名域名micDataSourceEntry" />

<bean id="dynamicDataSource" class="域名域名域名micDataSource" >
   <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 域名.test;

import 域名.域名erDao;
import 域名.域名rDao;
import 域名.域名er;
import 域名.域名r;
import 域名re;
import 域名;
import 域名域名ith;
import 域名域名域名wired;
import 域名.域名extConfiguration;
import 域名.域名ngJUnit4ClassRunner;

import 域名leDateFormat;
import 域名ys;
import 域名;
import 域名;

@ContextConfiguration(locations = {"classpath:application-域名"})
@RunWith(域名s)
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 = 域名ctAll();
            域名tln(域名ring(域名ray()));
        } catch (Exception e) {
            域名tStackTrace();
        }
    }

    @Test
    @Ignore
    public void testInsertMember(){
        try {
            for (int age = 25; age < 35; age++) {
                Member member = new Member();
                域名ge(age);
                域名ame("Tom");
                域名ddr("Hunan Changsha");
                域名rt(member);
            }
        }catch (Exception e){
            域名tStackTrace();
        }

    }


    @Test
// @Ignore
    public void testInsertOrder(){
        try {
            Order order = new Order();
            域名emberId(1L);
            域名etail("历史订单");
            Date date = 域名e("20180201123456");
            域名reateTime(域名ime());
            域名rtOne(order);
        }catch (Exception e){
            域名tStackTrace();
        }
    }

}

所谓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弹架构”原创,转载请注明出处。技术在于分享,我分享我快乐!
如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力。

原创不易,坚持很酷,都看到这里了,小伙伴记得点赞、收藏、在看,一键三连加关注!如果你觉得内容太干,可以分享转发给朋友滋润滋润!

标签:编程
湘ICP备14001474号-3  投诉建议:234161800@qq.com   部分内容来源于网络,如有侵权,请联系删除。