动态切换数据源总结学习

        要实现动态数据源切换,离不开Spring框架的AbstractRoutingDataSource这个抽象类。这是实现动态数据源切换的关键。我们先看下这个抽象类。

        AbstractRoutingDataSource的源码如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.jdbc.datasource.lookup;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collections;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.datasource.AbstractDataSource;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

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 AbstractRoutingDataSource() {
    }

    public void setTargetDataSources(Map<Object, Object> targetDataSources) {
        this.targetDataSources = targetDataSources;
    }

    public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
        this.defaultTargetDataSource = defaultTargetDataSource;
    }

    public void setLenientFallback(boolean lenientFallback) {
        this.lenientFallback = lenientFallback;
    }

    public void setDataSourceLookup(@Nullable DataSourceLookup dataSourceLookup) {
        this.dataSourceLookup = (DataSourceLookup)(dataSourceLookup != null ? dataSourceLookup : new JndiDataSourceLookup());
    }

    public void afterPropertiesSet() {
        if (this.targetDataSources == null) {
            throw new IllegalArgumentException("Property 'targetDataSources' is required");
        } else {
            this.resolvedDataSources = CollectionUtils.newHashMap(this.targetDataSources.size());
            this.targetDataSources.forEach((key, value) -> {
                Object lookupKey = this.resolveSpecifiedLookupKey(key);
                DataSource dataSource = this.resolveSpecifiedDataSource(value);
                this.resolvedDataSources.put(lookupKey, dataSource);
            });
            if (this.defaultTargetDataSource != null) {
                this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);
            }

        }
    }

    protected Object resolveSpecifiedLookupKey(Object lookupKey) {
        return lookupKey;
    }

    protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
        if (dataSource instanceof DataSource) {
            return (DataSource)dataSource;
        } else if (dataSource instanceof String) {
            return this.dataSourceLookup.getDataSource((String)dataSource);
        } else {
            throw new IllegalArgumentException("Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);
        }
    }

    public Map<Object, DataSource> getResolvedDataSources() {
        Assert.state(this.resolvedDataSources != null, "DataSources not resolved yet - call afterPropertiesSet");
        return Collections.unmodifiableMap(this.resolvedDataSources);
    }

    @Nullable
    public DataSource getResolvedDefaultDataSource() {
        return this.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);
    }

    public <T> T unwrap(Class<T> iface) throws SQLException {
        return iface.isInstance(this) ? this : this.determineTargetDataSource().unwrap(iface);
    }

    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return iface.isInstance(this) || this.determineTargetDataSource().isWrapperFor(iface);
    }

    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接口。InitializingBean接口提供了一个afterPropertiesSet()方法,凡是继承了该接口的类那么在初始化Bean的时候就会执行该方法。所以AbstractRoutingDataSource类中的afterPropertiesSet正是用于初始化一些信息。要想了解InitializingBean这个接口,可以参考我的这篇blog:Spring框架中InitializingBean的作用 

        其次,这个类AbstractRoutingDataSource类有个抽象方法,这个抽象方法需要我们实现,这个方法的作用就是指定用哪个数据源,也就是告诉dao层现在连接数据源用我们指定的数据源了。

                

protected abstract Object determineCurrentLookupKey();

        AbstractRoutingDataSource还继承了抽象类AbstractDataSource,而AbstractDataSource抽象类又继承了DataSource接口,在DataSource中只有两个方法,这两个方法会在AbstractDataSource抽象类中得到具体实现。
        

        现在看看AbstractRoutingDataSource中的一些属性都有什么作用

@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;存放真正的数据源信息,将targetDataSources的信息copy一份
    @Nullable
    private DataSource resolvedDefaultDataSource; 默认数据源,将defaultTargetDataSource转为DataSource赋值给resolvedDefaultDataSource

    public AbstractRoutingDataSource() {
    }

           AbstractRoutingDataSource抽象类中afterPropertiesSet()方法是核心,那看看他到底做了哪些初始化操作。

        

public void afterPropertiesSet() {
        if (this.targetDataSources == null) {
            throw new IllegalArgumentException("Property 'targetDataSources' is required");
        } else {
            this.resolvedDataSources = CollectionUtils.newHashMap(this.targetDataSources.size());
            this.targetDataSources.forEach((key, value) -> {
                Object lookupKey = this.resolveSpecifiedLookupKey(key);
                DataSource dataSource = this.resolveSpecifiedDataSource(value);
                this.resolvedDataSources.put(lookupKey, dataSource);
            });
            if (this.defaultTargetDataSource != null) {
                this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);
            }

        }
    }

        第一,当targetDataSources为空时,会抛出IllegalArgumentException错误,所以我们在配置多数据源时,至少需要传入一个数据源。

        第二, 初始化了resolvedDataSources大小,resolvedDataSources只是把targetDataSources的内容copy了一份,不同之处在于targetDataSources的value是Object类型,而resolvedDataSources的value是DataSource类型。
        第三,遍历targetDataSources集合,然后调用了resolveSpecifiedLookupKey()方法和resolveSpecifiedDataSource()方法,最后将返回值当作Key-Value放入resolvedDataSources集合中。

        第四,那么方法resolveSpecifiedLookupKey()和方法resolveSpecifiedDataSource()分别做了什么呢?我们可以查看两个方法的实现。其实可以看到resolveSpecifiedLookupKey并没有做什么操作就直接返回了值,而resolveSpecifiedDataSource只是把Object转为DataSource对象返回。
        

        所以afterPropertiesSet()初始话就是将targetDataSources的内容转化一下放到resolvedDataSources中,将defaultTargetDataSource转为DataSource赋值给resolvedDefaultDataSource

        我们再看getConnection()方法是如何实现的。调用了determineTargetDataSource()方法

        

public Connection getConnection() throws SQLException {
        return this.determineTargetDataSource().getConnection();
    }

        那么determineTargetDataSource方法到底做了什么,我们可以看一下determineTargetDataSource的实现。该方法返回DataSource类型的对象,并且调用了determineCurrentLookupKey方法,可能这时有人发现了这个方法就是我们自定义数据源类要实现的那个方法。determineCurrentLookupKey返回一个Object,命名为lookupKey,将lookupKey作为key,到resolvedDataSources集合中去拿数据源,如果没有拿到数据源,那么它会拿默认数据源resolvedDefaultDataSource。如果还是没有拿到,此时就报错啦!

        通过上面的分析,我们自己实现的方法determineCurrentLookupKey()是什么时候别调用的呢?从逻辑上看determineCurrentLookupKey()被determineTargetDataSource()调用,而determineTargetDataSource()被getConnection()调用,那getConnection()方法是被谁在什么时候调用的呢?通过代码跟踪,是我们在service层调用dao层方法时,这时候调用的。

        

        了解了AbstractRoutingDataSource这个抽象类,也就该实现一个动态数据源切换功能了,涉及的源码如下:

        

package com.lsl.mylsl.config;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * 动态数据源实现类
 */
public class DynamicDataSource extends AbstractRoutingDataSource {


    @Override
    protected Object determineCurrentLookupKey() {
        String dataSource = DBContextHolder.getDataSource();
        if (dataSource == null || "".equals(dataSource)){
            dataSource = "ds1";
        }
        return dataSource;
    }
}

        当没有指定数据源时,默认指定ds1为当前数据源

        

package com.lsl.mylsl.config;

public class DBContextHolder {

    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    /**
     * 动态切换数据源
     * @param dataSource
     */
    public static void setDataSource(String dataSource){

        if (SpringbootConfig.DBMAP.containsKey(dataSource)){
            CONTEXT_HOLDER.set(dataSource);
        }else {
            System.out.println("数据源" + dataSource + "不存在");
        }
    }

    /**
     * 获取数据源
     * @return
     */
    public static String getDataSource(){
        return CONTEXT_HOLDER.get();
    }

    /**
     * 清空数据源
     */
    public static void clearDataSource(){
        CONTEXT_HOLDER.remove();
    }
}

        

        

package com.lsl.mylsl.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * 初始化多个数据源
 * 注册动态数据源
 */
@Configuration
public class SpringbootConfig {

    public static final Map<Object,Object> DBMAP = new HashMap<>();

    /**
     * 初始化数据源1
     */
    public void initDataSource1(){
        try {
            DruidDataSource dds1 = DruidDataSourceBuilder.create().build();
            dds1.setUsername("admin");
            dds1.setPassword("123456");
            dds1.setUrl("jdbc:oracle:thin:@10.10.10.10:1521/oracle1");
            dds1.setInitialSize(5);
            dds1.setMinIdle(5);
            dds1.setMaxActive(20);
            dds1.setMaxWait(60000);
            dds1.setTimeBetweenEvictionRunsMillis(60000);
            dds1.setMinEvictableIdleTimeMillis(300000);
            dds1.setValidationQuery("SELECT * FROM DUAL");
            dds1.setTestWhileIdle(true);
            dds1.setTestOnBorrow(false);
            dds1.setTestOnReturn(false);
            dds1.setMaxPoolPreparedStatementPerConnectionSize(20);
            dds1.setFilters("stat,wall");
            dds1.setConnectionProperties("druid.stat.mergeSql=true;druid.stat.slow.slowSqlMillis=5000");
            dds1.setUseGlobalDataSourceStat(true);
            //添加数据源到map
            SpringbootConfig.DBMAP.put("ds1",dds1);
        } catch (Exception e) {

        }


    }

    /**
     * 初始化数据源2
     */
    public void initDataSource2(){
        try {
            DruidDataSource dds2 = DruidDataSourceBuilder.create().build();
            dds2.setUsername("admin");
            dds2.setPassword("123456");
            dds2.setUrl("jdbc:mysql://10.10.10.10:1521/mysql1");
            dds2.setInitialSize(5);
            dds2.setMinIdle(5);
            dds2.setMaxActive(20);
            dds2.setMaxWait(60000);
            dds2.setTimeBetweenEvictionRunsMillis(60000);
            dds2.setMinEvictableIdleTimeMillis(300000);
            dds2.setValidationQuery("SELECT 1");
            dds2.setTestWhileIdle(true);
            dds2.setTestOnBorrow(false);
            dds2.setTestOnReturn(false);
            dds2.setMaxPoolPreparedStatementPerConnectionSize(20);
            dds2.setFilters("stat,wall");
            dds2.setConnectionProperties("druid.stat.mergeSql=true;druid.stat.slow.slowSqlMillis=5000");
            dds2.setUseGlobalDataSourceStat(true);
            dds2.setDbType("mysql");
            //添加数据源到map
            SpringbootConfig.DBMAP.put("ds2",dds2);
        } catch (Exception e) {

        }
    }

    /**
     * 把DynamicDataSource交给spring托管
     * @return
     */
    @Bean
    public DataSource dynamicDataSource(){
        DynamicDataSource myDs = new DynamicDataSource();
        initDataSource1();
        initDataSource2();
        myDs.setTargetDataSources(SpringbootConfig.DBMAP);
        myDs.afterPropertiesSet();
        return myDs;
    }

    /**
     * 事务管理
     * @return
     */
    @Bean
    public PlatformTransactionManager transactionManager(){
        return new DataSourceTransactionManager(dynamicDataSource());
    }
}

        如何实现数据源切换,代码很简单,如下:

package com.lsl.mylsl.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lsl.mylsl.BO.CatBO;
import com.lsl.mylsl.config.DBContextHolder;
import com.lsl.mylsl.mapper.CatMapper;
import com.lsl.mylsl.service.ICatService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;

@Service
public class CatServiceImpl extends ServiceImpl<CatMapper, CatBO> implements ICatService {

    @Autowired
    CatMapper catMapper;

    @Override
    public List<Map> qryCatByAge(Map params) {
        //切换数据源到ds2
        DBContextHolder.setDataSource("ds2");
        return catMapper.qryCatByAge(params);
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 称重管理系统VB源代码是一个用Visual Basic编写的程序,用于管理和记录物品的重量信息。这个系统可以帮助用户迅速、准确地测量物品重量,并将数据存储在数据库或文件中,以便于日后查询和分析。 系统的主要功能包括: 1. 添加物品信息:用户可以输入物品的名称、型号、重量等信息,并保存到数据库中。 2. 称重功能:系统提供称重功能,用户可以连接称重仪器,测量物品的重量,并自动将结果保存到数据库中。 3. 查询和统计功能:用户可以根据物品名称、日期等条件查询特定物品的重量记录,并进行统计分析,如计算平均重量、最大重量等。 4. 修改和删除功能:用户可以对已保存的物品信息进行修改和删除操作,以更新或删除不需要的记录。 5. 数据导出功能:用户可以将查询得到的数据导出为Excel表格或文本文件,方便进行进一步的处理和分析。 6. 用户权限管理:系统可以设置不同用户的权限,如管理员、普通用户等,以保证数据的安全性和隐私性。 该系统使用Visual Basic编程语言开发,具有较好的可扩展性和可维护性。用户界面友好,操作简单易懂,减少用户的学习成本。源代码中包含了称重仪器的接口和数据处理逻辑,保证了称重数据的准确性和可靠性。 总结而言,称重管理系统VB源代码是一个功能完善的物品重量管理系统,旨在提高称重过程的效率和准确性,并方便用户进行数据查询和分析。 ### 回答2: 对于称重管理系统的VB源代码,以下是一份基础的代码示例: 首先,你可以创建一个Form来放置界面元素。在这个Form中,你可以添加一组文本框、按钮和标签来实现用户输入和显示结果。例如,你可以在Form中添加一个文本框用于输入物品的重量,并添加一个按钮来触发计算和显示结果。 接下来,在按钮的Click事件中,你可以编写代码来实现具体的功能。例如,你可以使用一个变量来存储文本框中输入的重量值,并使用条件语句来判断重量值是否符合要求。如果符合要求,可以进行计算并输出结果;如果不符合要求,可以给出错误提示。 除此之外,你可能需要使用其他控件来实现更复杂的功能。例如,如果你需要记录和显示多个物品的重量,可以使用一个数据网格控件来展示所有的输入和计算结果。你还可以添加其他按钮和功能来实现数据的保存、导入和导出等操作。 最后,为了保证系统的稳定性和用户友好性,你还可以添加一些输入验证和错误处理的功能。例如,可以检查输入是否为空或非数字,如果有错误可以给出相应提示并要求用户重新输入。 总的来说,称重管理系统的VB源代码可以根据具体的功能和需求来编写。以上只是一个简单的示例,你可以根据自己的需求进行扩展和修改。希望对你有所帮助! ### 回答3: 称重管理系统是一种用于管理货物称重的软件系统。它基于VB编程语言开发,具有方便易用、功能强大、操作简单等特点。 称重管理系统的源代码主要包括以下几个方面的功能: 1. 设备连接:系统可以通过串口或USB接口与称重设备进行连接,实现称重数据的传输与接收。 2. 数据录入:系统可以录入货物的基本信息,如名称、重量、规格等,并将其存储在数据库中,方便后续的管理与查询。 3. 数据查询:系统可以根据用户的需求,通过关键字查询货物信息,例如根据名称查询该货物的重量或规格等相关数据。 4. 数据统计:系统可以根据一定的统计规则,对货物的重量进行统计分析,生成报表或图表,帮助用户更好地了解货物的分布情况。 5. 数据导出:系统可以将查询到的数据进行导出,以便用户将数据保存到Excel或其他格式的文件中,方便后续的操作和使用。 6. 用户管理:系统可以进行用户权限的管理,设置不同用户的权限级别,保证数据的安全性与合法性。 7. 系统设置:系统具有一些基本的设置功能,如界面的调整、语言的切换、数据备份与恢复等,提升用户的使用体验。 通过以上的功能,称重管理系统可以实现货物的快速录入、精确称重、数据查询与分析等功能,提高称重效率,减少人工错误,同时也方便用户在重量数据方面进行合理的管理和决策。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一路奔跑1314

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值