Spring Boot + MySQL 多线程查询与联表查询性能对比分析

Spring Boot + MySQL: 多线程查询与联表查询性能对比分析

背景

在现代 Web 应用开发中,数据库性能是影响系统响应时间和用户体验的关键因素之一。随着业务需求的不断增长,单表查询和联表查询的效率问题日益凸显。特别是在 Spring Boot 项目中,结合 MySQL 数据库进行复杂查询时,如何优化查询性能已成为开发者必须面对的重要问题。

在本实验中,我们使用了 Spring Boot 框架结合 MySQL 数据库,进行了两种常见查询方式的性能对比:多线程查询联表查询。通过对比这两种查询方式的响应时间,本文旨在探讨在实际业务场景中,选择哪种方式能带来更高的查询效率,尤其是在面对大数据量和复杂查询时的性能表现。


实验目的

本实验的主要目的是通过对比以下两种查询方式的性能,帮助开发者选择在不同业务场景下的查询方式:

  1. 联表查询(使用 SQL 语句中的 LEFT JOIN 等连接操作)
  2. 多线程查询(通过 Spring Boot 异步处理,分批查询不同表的数据)

实验环境

  • 开发框架:Spring Boot

  • 数据库:MySQL

  • 数据库表结构

    • test_a:主表,包含与其他表(test_btest_ctest_dtest_e)的关联字段。
    • test_btest_ctest_dtest_e:附表,分别包含不同的数据字段。

    这些表通过外键关联,test_a 表中的 test_b_idtest_c_idtest_d_idtest_e_id 字段指向各自的附表。

  • 数据量:约 100,000 条数据,分别在主表和附表中填充数据。

一.建表语句

主表A

CREATE TABLE `test_a` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `description` varchar(255) DEFAULT NULL,
  `test_b_id` int DEFAULT NULL,
  `test_c_id` int DEFAULT NULL,
  `test_d_id` int DEFAULT NULL,
  `test_e_id` int DEFAULT NULL,
  `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

附表b,c,d,e

CREATE TABLE `test_b` (
  `id` int NOT NULL AUTO_INCREMENT,
  `field_b1` varchar(255) DEFAULT NULL,
  `field_b2` int DEFAULT NULL,
  `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=792843462 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

CREATE TABLE `test_c` (
  `id` int NOT NULL AUTO_INCREMENT,
  `field_c1` varchar(255) DEFAULT NULL,
  `field_c2` datetime DEFAULT NULL,
  `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=100096 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

CREATE TABLE `test_d` (
  `id` int NOT NULL AUTO_INCREMENT,
  `field_d1` text,
  `field_d2` tinyint(1) DEFAULT NULL,
  `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=100300 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;


CREATE TABLE `test_e` (
  `id` int NOT NULL AUTO_INCREMENT,
  `field_e1` int DEFAULT NULL,
  `field_e2` varchar(255) DEFAULT NULL,
  `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=100444 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

二.填充数据

@SpringBootTest
class DemoTestQuerySpringbootApplicationTests {

    @Autowired
    private TestAMapper testAMapper;
    @Autowired
    private TestBMapper testBMapper;
    @Autowired
    private TestCMapper testCMapper;
    @Autowired
    private TestDMapper testDMapper;
    @Autowired
    private TestEMapper testEMapper;

    @Test
    void contextLoads() {
        // 随机数生成器
        Random random = new Random();

        for (int i = 1; i <= 100000; i++) {
            // 插入 test_b 数据
            int testBId = insertTestB(random);

            // 插入 test_c 数据
            int testCId = insertTestC(random);

            // 插入 test_d 数据
            int testDId = insertTestD(random);

            // 插入 test_e 数据
            int testEId = insertTestE(random);

            // 插入 test_a 数据
            insertTestA(testBId, testCId, testDId, testEId, random);
        }
    }

    private int insertTestB(Random random) {
        TestB testB = new TestB();
        testB.setFieldB1("B Field " + random.nextInt(1000));
        testB.setFieldB2(random.nextInt(1000));
        testBMapper.insert(testB);  // 插入数据
        return testB.getId();  
    }

    private int insertTestC(Random random) {
        TestC testC = new TestC();
        testC.setFieldC1("C Field " + random.nextInt(1000));
        testC.setFieldC2(new java.sql.Timestamp(System.currentTimeMillis()));
        testCMapper.insert(testC);  // 插入数据
        return testC.getId();  
    }

    private int insertTestD(Random random) {
        TestD testD = new TestD();
        testD.setFieldD1("D Field " + random.nextInt(1000));
        testD.setFieldD2(random.nextBoolean());
        testDMapper.insert(testD);  // 插入数据
        return testD.getId();  
    }

    private int insertTestE(Random random) {
        TestE testE = new TestE();
        testE.setFieldE1(random.nextInt(1000));
        testE.setFieldE2("E Field " + random.nextInt(1000));
        testEMapper.insert(testE);  // 插入数据
        return testE.getId();  
    }

    private void insertTestA(int testBId, int testCId, int testDId, int testEId, Random random) {
        TestA testA = new TestA();
        testA.setName("Test A Name " + random.nextInt(1000));
        testA.setDescription("Test A Description " + random.nextInt(1000));
        testA.setTestBId(testBId);
        testA.setTestCId(testCId);
        testA.setTestDId(testDId);
        testA.setTestEId(testEId);
        testAMapper.insert(testA);  // 插入数据
    }

}

三.配置线程池

3.1配置

/**
 * 实现AsyncConfigurer接口
 * 并重写了 getAsyncExecutor方法,
 * 这个方法返回 myExecutor(),
 * Spring 默认会将 myExecutor 作为 @Async 方法的线程池。
 */
@Configuration
@EnableAsync
public class ThreadPoolConfig implements AsyncConfigurer {
    /**
     * 项目共用线程池
     */
    public static final String TEST_QUERY = "testQuery";


    @Override
    public Executor getAsyncExecutor() {
        return myExecutor();
    }

    @Bean(TEST_QUERY)
    @Primary
    public ThreadPoolTaskExecutor myExecutor() {
        //spring的线程池
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //线程池优雅停机的关键
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(200);
        executor.setThreadNamePrefix("my-executor-");
        //拒绝策略->满了调用线程执行,认为重要任务
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //自己就是一个线程工程
        executor.setThreadFactory(new MyThreadFactory(executor));
        executor.initialize();
        return executor;
    }

}

3.2异常处理

public class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
    private static final Logger log = LoggerFactory.getLogger(MyUncaughtExceptionHandler.class);

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        log.error("Exception in thread",e);
    }
}

3.3线程工厂

@AllArgsConstructor
public class MyThreadFactory implements ThreadFactory {

    private static final MyUncaughtExceptionHandler MyUncaughtExceptionHandler = new MyUncaughtExceptionHandler();
    private ThreadFactory original;

    @Override
    public Thread newThread(Runnable r) {
        //执行Spring线程自己的创建逻辑
        Thread thread = original.newThread(r);
        //我们自己额外的逻辑
        thread.setUncaughtExceptionHandler(MyUncaughtExceptionHandler);
        return thread;
    }
}

四.Service查询方法

4.1left join连接查询

    @Override
    public IPage<TestAll> getTestAllPage_1(int current, int size) {
        // 创建 Page 对象,current 为当前页,size 为每页大小
        Page<TestAll> page = new Page<>(current, size);
        return testAMapper.selectAllWithPage(page);
    }

对应的xml 的sql语句

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.fth.demotestqueryspringboot.com.test.mapper.TestAMapper">

  <!-- 基本的 ResultMap 映射 -->
  <resultMap id="BaseResultMap" type="org.fth.demotestqueryspringboot.com.test.entity.vo.TestAll">
    <id column="test_a_id" jdbcType="INTEGER" property="testAId" />
    <result column="name" jdbcType="VARCHAR" property="name" />
    <result column="description" jdbcType="VARCHAR" property="description" />
    <result column="test_b_id" jdbcType="INTEGER" property="testBId" />
    <result column="test_c_id" jdbcType="INTEGER" property="testCId" />
    <result column="test_d_id" jdbcType="INTEGER" property="testDId" />
    <result column="test_e_id" jdbcType="INTEGER" property="testEId" />
    <result column="created_at" jdbcType="TIMESTAMP" property="createdAt" />
    <result column="updated_at" jdbcType="TIMESTAMP" property="updatedAt" />
    <!-- TestB -->
    <result column="field_b1" jdbcType="VARCHAR" property="fieldB1" />
    <result column="field_b2" jdbcType="INTEGER" property="fieldB2" />
    <result column="test_b_created_at" jdbcType="TIMESTAMP" property="testBCreatedAt" />
    <!-- TestC -->
    <result column="field_c1" jdbcType="VARCHAR" property="fieldC1" />
    <result column="field_c2" jdbcType="TIMESTAMP" property="fieldC2" />
    <result column="test_c_created_at" jdbcType="TIMESTAMP" property="testCCreatedAt" />
    <!-- TestD -->
    <result column="field_d1" jdbcType="VARCHAR" property="fieldD1" />
    <result column="field_d2" jdbcType="BOOLEAN" property="fieldD2" />
    <result column="test_d_created_at" jdbcType="TIMESTAMP" property="testDCreatedAt" />
    <!-- TestE -->
    <result column="field_e1" jdbcType="INTEGER" property="fieldE1" />
    <result column="field_e2" jdbcType="VARCHAR" property="fieldE2" />
    <result column="test_e_created_at" jdbcType="TIMESTAMP" property="testECreatedAt" />
  </resultMap>

  <!-- 分页查询 TestA 和其他表的数据 -->
  <select id="selectAllWithPage" resultMap="BaseResultMap">
    SELECT
    a.id AS test_a_id,
    a.name,
    a.description,
    a.test_b_id,
    a.test_c_id,
    a.test_d_id,
    a.test_e_id,
    a.created_at,
    a.updated_at,
    -- TestB
    b.field_b1,
    b.field_b2,
    b.created_at AS test_b_created_at,
    -- TestC
    c.field_c1,
    c.field_c2,
    c.created_at AS test_c_created_at,
    -- TestD
    d.field_d1,
    d.field_d2,
    d.created_at AS test_d_created_at,
    -- TestE
    e.field_e1,
    e.field_e2,
    e.created_at AS test_e_created_at
    FROM test_a a
    LEFT JOIN test_b b ON a.test_b_id = b.id
    LEFT JOIN test_c c ON a.test_c_id = c.id
    LEFT JOIN test_d d ON a.test_d_id = d.id
    LEFT JOIN test_e e ON a.test_e_id = e.id

  </select>

</mapper>

4.2多线程查询

 @Override
    public IPage<TestAll> getTestAllPage_2(int current, int size) {
        IPage<TestA> testAPage = testAMapper.selectPage(new Page<>(current, size), null);
        List<TestA> testAS = testAPage.getRecords();
        CompletableFuture<List<TestB>> futureBs = selectTestBids(testAS.stream().map(TestA::getTestBId).collect(Collectors.toSet()));
        CompletableFuture<List<TestC>> futureCs = selectTestCids(testAS.stream().map(TestA::getTestCId).collect(Collectors.toSet()));
        CompletableFuture<List<TestD>> futureDs = selectTestDids(testAS.stream().map(TestA::getTestDId).collect(Collectors.toSet()));
        CompletableFuture<List<TestE>> futureEs = selectTestEids(testAS.stream().map(TestA::getTestEId).collect(Collectors.toSet()));
        // 等待所有异步任务完成并收集结果
        CompletableFuture<Void> allFutures = CompletableFuture.allOf(futureBs, futureCs, futureDs, futureEs);

        try {
            // 等待所有异步任务完成
            allFutures.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
            throw new RuntimeException("Failed to fetch data", e);
        }
        // 获取异步查询的结果
        List<TestB> bs = futureBs.join();
        List<TestC> cs = futureCs.join();
        List<TestD> ds = futureDs.join();
        List<TestE> es = futureEs.join();

        // 将结果映射到Map以便快速查找
        Map<Integer, TestB> bMap = bs.stream().collect(Collectors.toMap(TestB::getId, b -> b));
        Map<Integer, TestC> cMap = cs.stream().collect(Collectors.toMap(TestC::getId, c -> c));
        Map<Integer, TestD> dMap = ds.stream().collect(Collectors.toMap(TestD::getId, d -> d));
        Map<Integer, TestE> eMap = es.stream().collect(Collectors.toMap(TestE::getId, e -> e));
        List<TestAll> testAllList = testAS.stream().map(testA -> {
            TestAll testAll = new TestAll();
            testAll.setTestAId(testA.getId());
            testAll.setName(testA.getName());
            testAll.setDescription(testA.getDescription());
            testAll.setCreatedAt(testA.getCreatedAt());

            // 根据 testBId 填充 TestB 的字段
            if (testA.getTestBId() != null) {
                TestB testB = bMap.get(testA.getTestBId());
                if (testB != null) {
                    testAll.setFieldB1(testB.getFieldB1());
                    testAll.setFieldB2(testB.getFieldB2());
                    testAll.setTestBCreatedAt(testB.getCreatedAt());
                }
            }

            // 根据 testCId 填充 TestC 的字段
            if (testA.getTestCId() != null) {
                TestC testC = cMap.get(testA.getTestCId());
                if (testC != null) {
                    testAll.setFieldC1(testC.getFieldC1());
                    testAll.setFieldC2(testC.getFieldC2());
                    testAll.setTestCCreatedAt(testC.getCreatedAt());
                }
            }

            // 根据 testDId 填充 TestD 的字段
            if (testA.getTestDId() != null) {
                TestD testD = dMap.get(testA.getTestDId());
                if (testD != null) {
                    testAll.setFieldD1(testD.getFieldD1());
                    testAll.setFieldD2(testD.getFieldD2());
                    testAll.setTestDCreatedAt(testD.getCreatedAt());
                }
            }

            // 根据 testEId 填充 TestE 的字段
            if (testA.getTestEId() != null) {
                TestE testE = eMap.get(testA.getTestEId());
                if (testE != null) {
                    testAll.setFieldE1(testE.getFieldE1());
                    testAll.setFieldE2(testE.getFieldE2());
                    testAll.setTestECreatedAt(testE.getCreatedAt());
                }
            }
            return testAll;
        }).collect(Collectors.toList());

        // 创建并返回新的分页对象
        IPage<TestAll> page = new Page<>(testAPage.getCurrent(), testAPage.getSize(), testAPage.getTotal());
        page.setRecords(testAllList);
        return page;
    }


    @Async
    public CompletableFuture<List<TestB>> selectTestBids(Set<Integer> bids) {
        return CompletableFuture.supplyAsync(() -> testBMapper.selectBatchIds(bids));
    }

    @Async
    public CompletableFuture<List<TestC>> selectTestCids(Set<Integer> cids) {
        return CompletableFuture.supplyAsync(() -> testCMapper.selectBatchIds(cids));
    }

    @Async
    public CompletableFuture<List<TestD>> selectTestDids(Set<Integer> dids) {
        return CompletableFuture.supplyAsync(() -> testDMapper.selectBatchIds(dids));
    }

    @Async
    public CompletableFuture<List<TestE>> selectTestEids(Set<Integer> eids) {
        return CompletableFuture.supplyAsync(() -> testEMapper.selectBatchIds(eids));
    }

五.结果测试

5.1连接查询

在这里插入图片描述

在这里插入图片描述

查询结果表格

currentsize响应时间
12016ms
502023ms
1002022ms
5002052ms
200200213ms
500200517ms

5.2多线程查询

在这里插入图片描述

在这里插入图片描述

查询结果表格

currentsize响应时间
12018ms
502017ms
1002017ms
5002021ms
20020056ms
50020080ms

总结与建议

  • 选择联表查询:当数据量较小,或者查询逻辑较为简单时,使用联表查询可以更简单直接,查询性能也较为优秀。
  • 选择多线程查询:当面对大数据量或者复杂查询时,采用多线程查询将带来更显著的性能提升。通过异步并行查询,可以有效缩短响应时间,提升系统的整体性能。

在实际开发中,可以根据具体的业务需求和数据库的规模,合理选择查询方式,从而提高数据库查询效率,优化系统性能

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/929627.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

人工智能学习用的电脑安装cuda、torch、conda等软件,版本的选择以及多版本切换

接触人工智能的学习三个月了&#xff0c;每天与各种安装包作斗争&#xff0c;缺少依赖包、版本高了、版本低了、不兼容了、系统做一半从头再来了。。。这些都是常态。三个月把单位几台电脑折腾了不下几十次安装&#xff0c;是时候总结一下踩过的坑和积累的经验了。 以一个典型的…

Vue工程化开发中各文件的作用

1.main.js文件 main.js文件的主要作用&#xff1a;导入App.vue&#xff0c;基于App.vue创建结构渲染index.html。

本地运行打包好的dist

首先输入打包命令 每个人设置不一样 一般人 是npm run build如果不知道可以去package.json里去看。 打包好文件如下 命令行输入 :npm i -g http-server 进入到dist目录下输入 命令cmd 输入 http-server 成功

华为HCIE-Datacom认证笔试+实验考试介绍

华为HCIE数通认证考试是面向那些希望成为数通网络领域专家的人员&#xff0c;考试通常两部分&#xff1a;笔试和实验考试。 考试科目&#xff1a; HCIE-Datacom笔试考试内容&#xff1a; HCIE-Datacom V1.0考试覆盖数据通信领域路由交换高阶技术、企业网络架构全景、园区网络…

【组件封装】uniapp vue3 封装一个完整的Tabs(标签页)组件教程,功能由简到杂实现讲解。

文章目录 前言一、简单版Tabs代码实现&#xff1a; 二、下划线带动画的TabsAPI回顾&#xff1a;代码实现&#xff1a; 三、内容区域滑动切换切换动画代码实现&#xff1a;&#xff08;2&#xff09;禁用手势滑动切换&#xff08;3&#xff09;内容区域换为插槽 四、标签栏可滚动…

相对路径和绝对路径与链接标签

一.相对路径 相对路径&#xff1a;以引用文件所在位置为参考基础&#xff0c;而建立出的目录路径。 即图片相对于你写的html页面的位置 相对路径分类符号说明同一级路径图片与html文件处于同一级&#xff0c;如<img src"baidu.gif">下一级路径/图片位于html…

【Java】Switch语句、循环语句(for、while、do...while)

Switch语句&#xff1a;针对某个表达式的值进行判断&#xff0c;从而决定执行哪一段代码 语法格式&#xff1a; switch(表达式){ case 目标值1: 执行语句1 break; case 目标值2: …

P3916 图的遍历(Tarjan缩点和反向建边)

P3916 图的遍历 - 洛谷 | 计算机科学教育新生态 写法一&#xff1a;Tarjan 思路&#xff1a;先运用Tarjan算法得到每个连通块中最大的编号&#xff0c;然后对每个连通块进行缩点重新建图&#xff0c;进行dfs&#xff0c;得到缩点后的连通块能够达到的最大编号。 Code: conste…

2024年认证杯SPSSPRO杯数学建模D题(第一阶段)AI绘画带来的挑战解题全过程文档及程序

2024年认证杯SPSSPRO杯数学建模 D题 AI绘画带来的挑战 原题再现&#xff1a; 2023 年开年&#xff0c;ChatGPT 作为一款聊天型AI工具&#xff0c;成为了超越疫情的热门词条&#xff1b;而在AI的另一个分支——绘图领域&#xff0c;一款名为Midjourney&#xff08;MJ&#xff…

同为科技(TOWE)柔性定制化PDU插座

随着科技的进步&#xff0c;越来越多的精密电子设备&#xff0c;成为工作生活密不可分的工具。 电子电气设备的用电环境也变得更为复杂&#xff0c;所以安全稳定的供电是电子电气设备的生命线。 插座插排作为电子电气设备最后十米范围内供配电最终核心部分&#xff0c;便捷、安…

GPS模块/SATES-ST91Z8LR:电路搭建;直接用电脑的USB转串口进行通讯;模组上报定位数据转换地图识别的坐标手动查询地图位置

从事嵌入式单片机的工作算是符合我个人兴趣爱好的,当面对一个新的芯片我即想把芯片尽快搞懂完成项目赚钱,也想着能够把自己遇到的坑和注意事项记录下来,即方便自己后面查阅也可以分享给大家,这是一种冲动,但是这个或许并不是原厂希望的,尽管这样有可能会牺牲一些时间也有哪天原…

设计模式阅读笔记

参考&#xff1a;设计模式目录&#xff1a;22种设计模式 设计模式是什么&#xff1f; 设计模式是软件设计中常见问题的典型解决方案。 它们就像能根据需求进行调整的预制蓝图&#xff0c; 可用于解决代码中反复出现的设计问题。 设计模式与方法或库的使用方式不同&#xff0c…

详尽的oracle sql函数

1&#xff0c;CHR 输入整数&#xff0c;返回对应字符。 用法&#xff1a;select chr(65),chr(78) from dual; 2&#xff0c;ASCII 输入字符&#xff0c;返回对应ASCII码。 用法&#xff1a;select ascii(A),ascii(B) from dual; 3&#xff0c;CONCAT 输入两个字符串&#xff0c…

C++小碗菜之二:软件单元测试

“没有测试的代码重构不能称之为重构&#xff0c;它仅仅是垃圾代码的到处移动” ——Corey Haines 目录 前言 什么是单元测试&#xff1f; 单元测试的组成 单元测试的命名 单元测试的独立性 Google Test 单元测试的环境配置与使用 1. Ubuntu下安装 Google Test 2. 编写…

Go 1.19.4 HTTP编程-Day 20

1. HTTP协议 1.1 基本介绍 HTTP协议又称超文本传输协议&#xff0c;属于应用层协议&#xff0c;在传输层使用TCP协议。HTTP协议属是无状态的&#xff0c;对事务处理没有记忆能力&#xff0c;如果需要保存状态需要引用其他技术&#xff0c;如Cookie。HTTP协议属是无连接的&…

【SpringBoot】使用IDEA创建SpringBoot项目

1、使用SpringBoot脚手架创建 我们使用SpringBoot的脚手架Spring Initializr创建&#xff0c;如图所示&#xff1a; 2、选择SpringBoot版本 最开始做项目时候&#xff0c;组长说创建一个 springboot 2.5.4 的项目&#xff0c;mysql使用 5.6.X &#xff0c;maven使用是3.6.X…

使用Oracle通过gateway连接MSSQL

环境概述 某医院的his系统Oracle数据库要和体检系统进行数据通讯&#xff0c;需要从Oracle能查到sqlserver的数据。本次通过Oracle gateway来解决此问题。 HIS服务器&#xff1a;windows server 2016数据库oracle11.2.0.4&#xff0c;假设IP是192.168.100.9 体检服务器&…

leetcode 之 二分查找(java)(2)

文章目录 74、搜索二维矩阵33、搜素旋转排序数组 74、搜索二维矩阵 题目描述&#xff1a; 给你一个满足下述两条属性的 m x n 整数矩阵&#xff1a; 每行中的整数从左到右按非严格递增顺序排列。每行的第一个整数大于前一行的最后一个整数。 给你一个整数 target &#xff…

Linux中的信号

目录 生活中的信号 Linux中的信号 前台进程与后台进程 信号的产生 核心转储 core dump ​编辑信号的其他相关概念 信号处理的三种方式 信号在内核中的表示示意图 sigset_t 类型 信号集操作函数 sigprocmask sigpending 综合练习 用户态与内核态 信号的捕捉过程 …

基于STM32F4实现步进电机闭环控制实现(无PID)

文章目录 概要整体流程代码实现TIM8 PWM控制TIM5 编码器计数TIM13 闭环控制 效果展示小结 概要 因客户外部负载较大&#xff0c;步进电机出现丢步现象&#xff0c;所以需要进行闭环控制&#xff0c;保证最后走到相应的位置即可&#xff0c;所以我采用的是电机停止后与编码器值…