30 уроков написанных от руки основных принципов динамического переключения источников данных Spring (8)

Java Spring

Эта статья взята из «Основных принципов Spring 5».

Прежде чем читать эту статью, пожалуйста, прочтите следующее:

30 уроков написанных от руки основных принципов пользовательского ORM Spring (на) (6)

30 классов написанных от руки основных принципов пользовательского ORM Spring (ниже) (7)

4 Основной принцип динамического переключения источников данных

Вот краткое введение в основные принципы AbstractRoutingDataSource. Функция реализации переключения источника данных заключается в настройке класса для расширения абстрактного класса AbstractRoutingDataSource, который фактически эквивалентен посреднику маршрутизации источника данных, который может переключаться на соответствующий источник данных в соответствии с соответствующим значением ключа при выполнении проекта. . Сначала взгляните на исходный код класса 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. Метод getConnection() класса AbstractRoutingDataSource вызывает этот метод для определенияTargetDataSource(). Здесь мы сосредоточимся на коде метода defineTargetDataSource(), который использует метод defineCurrentLookupKey(), который является абстрактным методом класса AbstractRoutingDataSource и методом для реализации расширений переключения источников данных. Возвращаемым значением этого метода является значение ключа источника данных, используемого в проекте.После получения значения ключа соответствующий источник данных может быть получен из разрешенного источника данных.Если источник данных, соответствующий ключу, не может быть найден, будет использоваться источник данных по умолчанию. использоваться. Когда пользовательский класс расширяет класс AbstractRoutingDataSource, ему необходимо переопределить метод defineCurrentLookupKey() для реализации переключения источников данных.

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 выглядит следующим образом:


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 выглядит следующим образом:


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 Relational Mapping, 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-фреймворка, базовый принцип динамического переключения источников данных и разбирается API-приложение Spring JdbcTemplate. Есть надежда, что благодаря изучению этой главы у «маленьких друзей» появятся лучшие идеи для решения проблем в повседневной работе и повысится эффективность работы.

Эта статья является оригиналом "Архитектуры бомбы Тома", пожалуйста, указывайте источник при перепечатке. Технология заключается в обмене, я разделяю свое счастье! Если у вас есть какие-либо предложения, вы также можете оставить комментарий или личное сообщение, Ваша поддержка является движущей силой для меня, чтобы упорствовать в создании. Обратите внимание на «архитектуру бомбы Тома», чтобы получить больше технической галантереи!

Нелегко быть оригинальным, и круто настаивать. Вы все это видели здесь. Не забудьте поставить лайк, добавить в закладки, посмотреть и подписаться одним щелчком мыши! Если вы считаете, что контент слишком сух, вы можете поделиться им и отправить своим друзьям, чтобы они питались и питались!