Mybaties-Plus saveBatch()、自定义批量插入、多线程批量插入性能测试和对比

一.背景

最近在做一个项目的时候,由于涉及到需要将一个系统的基础数据全量同步到另外一个系统中去,结果一看,基础数据有十几万条,作为小白的我,使用单元测试,写了一段代码,直接采用了MP(Mybaties-Plus)自带的saveBatch()方法,将基础数据导入到新的系统中去,但是后面涉及多次修正基础数据的情况,导致,每次重新插入数据或者更新的时候,都需要花费十几分钟的时间,后面想着以下的方案进行了优化。
其实针对自带的saveBatch()方法插入很慢,一般都是由于数据库连接url上没有配置批量操作的属性,只需要在url上加上如下属性即可,如下:

rewriteBatchedStatements=true

在配置数据库连接信息的时候,配置类似如下:

jdbc:mysql://数据库地址/数据库名?useUnicode=true&characterEncoding=UTF8&allowMultiQueries=true&rewriteBatchedStatements=true

加上之后,你就会发现,saveBatch的速度直线提升,效果还是很不错的,一万条数据估计也就在几百毫秒。
接下来的文章都是设置在rewriteBatchedStatements=false情况下,且MP(Mybaties-Plus)为3.5.3.1版本下进行测试的。

二 .优化方法

如果在 rewriteBatchedStatements=false情况下,使用自带的方法,插入几十万数据是比较慢的,我们先讲解自带的方法,再讲解MP给我们自定义空间的自定义方法,然后在加入一些多线程的情况下进行的测试和方案比较。

2.1 Mybaties-plus自带的批量saveBatch()方法

直接上代码
实体类如下:

@Data
@TableName("test_user")
public class TestUser implements Serializable {
    private String id;
    private String name;
    private String managerId;
    private String salary;
    private String age;
    private String departId;
    private String remark;
    private String province;
}

Mapper如下:

public interface TestUserMapper extends BaseMapper<TestUser> {
}

Service如下:

public interface ITestUserService extends IService<TestUser> {
}

@Service
public class TestUserServiceImpl extends ServiceImpl<TestUserMapper, TestUser> implements ITestUserService {
}

接下来我使用单元测试的方法,构造200000条数据,测试Mybaties-Plus自带的saveBatch()方法,代码如下:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,classes = JeecgSystemApplication.class)
public class UserTest {

    @Autowired
    private ITestUserService userService;

    @Test
    public void testInsertBatch(){
        List<TestUser> userList = new ArrayList<>();
        for(int i = 0; i < 199999; i++){
            TestUser user = new TestUser();
            user.setName("张三");
            user.setAge("20");
            user.setProvince("重庆市");
            user.setSalary("200000");
            user.setRemark("diitch");
            userList.add(user);
        }
        long s = System.currentTimeMillis();
        userService.saveBatch(userList);
        System.out.println("保存200000条数据消耗" + (System.currentTimeMillis() - s) + "ms");
    }
    }

测试结果如下,大概需要10s中的时间:
在这里插入图片描述
我们可以跟踪源码,它的实现如下:

  default boolean saveBatch(Collection<T> entityList) {
        return this.saveBatch(entityList, 1000);
    }

 public boolean saveBatch(Collection<T> entityList, int batchSize) {
        String sqlStatement = this.getSqlStatement(SqlMethod.INSERT_ONE);
        return this.executeBatch(entityList, batchSize, (sqlSession, entity) -> {
            sqlSession.insert(sqlStatement, entity);
        });
    }

       public static <E> boolean executeBatch(Class<?> entityClass, Log log, Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
        Assert.isFalse(batchSize < 1, "batchSize must not be less than one", new Object[0]);
        return !CollectionUtils.isEmpty(list) && executeBatch(entityClass, log, (sqlSession) -> {
            int size = list.size();
            int idxLimit = Math.min(batchSize, size);
            int i = 1;

            for(Iterator var7 = list.iterator(); var7.hasNext(); ++i) {   ## 循环执行
                E element = var7.next();
                consumer.accept(sqlSession, element);
                if (i == idxLimit) {
                    sqlSession.flushStatements();
                    idxLimit = Math.min(idxLimit + batchSize, size);
                }
            }

        });
    }
2.2 自定义批量插入或者更新的方法

直接上代码,首先我们自定义一个RootMapper, 继承BaseMapper,自定义自己的批量插入或者更新方法,如下:

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import java.util.Collection;

/**
 * @author diitich
 * @param <T>
 */
public interface RootMapper<T> extends BaseMapper<T> {

    /**
     * 批量新增
     * @param batchList
     * @return
     */
    int insertBatch(@Param("list") Collection<T> batchList);

    /**
     * 批量跟新
     * @param batchList
     * @return
     */
    int updateBatch(@Param("list")Collection<T> batchList);

}

定义InsertBatchColumn 继承 AbstractMethod ,下面基本就是一些通用的写法,不同的Mybatis-plus有一点点区别,本文用的版本为3.5.3.1版本,代码如下:

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.sql.SqlScriptUtils;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.executor.keygen.NoKeyGenerator;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import java.util.List;
import java.util.function.Predicate;

@Slf4j
public class InsertBatchColumn extends AbstractMethod {

    @Setter
    @Accessors(chain = true)
    private Predicate<TableFieldInfo> predicate;

    public InsertBatchColumn() {
        super("insertBatch");
    }

    public InsertBatchColumn(Predicate<TableFieldInfo> predicate) {
        // 此处的名称必须与后续的RootMapper的新增方法名称一致
        super("insertBatch");
        this.predicate = predicate;
    }

    @SuppressWarnings("Duplicates")
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;
        SqlMethod sqlMethod = SqlMethod.INSERT_ONE;
        List<TableFieldInfo> fieldList = tableInfo.getFieldList();
        String insertSqlColumn = tableInfo.getKeyInsertSqlColumn(true,false) +
                this.filterTableFieldInfo(fieldList, predicate, TableFieldInfo::getInsertSqlColumn, EMPTY);
        String columnScript = LEFT_BRACKET + insertSqlColumn.substring(0, insertSqlColumn.length() - 1) + RIGHT_BRACKET;
        String insertSqlProperty = tableInfo.getKeyInsertSqlProperty(true,ENTITY_DOT, false) +
                this.filterTableFieldInfo(fieldList, predicate, i -> i.getInsertSqlProperty(ENTITY_DOT), EMPTY);
        insertSqlProperty = LEFT_BRACKET + insertSqlProperty.substring(0, insertSqlProperty.length() - 1) + RIGHT_BRACKET;
        String valuesScript = SqlScriptUtils.convertForeach(insertSqlProperty, "list", null, ENTITY, COMMA);
        String keyProperty = null;
        String keyColumn = null;
        // 表包含主键处理逻辑,如果不包含主键当普通字段处理
        if (tableInfo.havePK()) {
            if (tableInfo.getIdType() == IdType.AUTO) {
                /* 自增主键 */
                keyGenerator = Jdbc3KeyGenerator.INSTANCE;
                keyProperty = tableInfo.getKeyProperty();
                keyColumn = tableInfo.getKeyColumn();
            } else {
                if (null != tableInfo.getKeySequence()) {
                    keyGenerator = TableInfoHelper.genKeyGenerator(modelClass.getName(), tableInfo, builderAssistant);
                    keyProperty = tableInfo.getKeyProperty();
                    keyColumn = tableInfo.getKeyColumn();
                }
            }
        }
        String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), columnScript, valuesScript);
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        // 注意第三个参数,需要与后续的RootMapper里面新增方法名称要一致,不然会报无法绑定异常
        return this.addInsertMappedStatement(mapperClass, modelClass, "insertBatch", sqlSource, keyGenerator, keyProperty, keyColumn);
    }
}

定义 UpdateBatchColumn 继承 AbstractMethod ,代码如下:

import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;

public class UpdateBatchColumn extends AbstractMethod {

    public UpdateBatchColumn(String methodName) {
        super(methodName);
    }

    @SuppressWarnings("Duplicates")
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        String sql = "<script>\n<foreach collection=\"list\" item=\"item\" separator=\";\">\nupdate %s %s where %s=#{%s} %s\n</foreach>\n</script>";
        String additional = tableInfo.isWithVersion() ? tableInfo.getVersionFieldInfo().getVersionOli("item", "item.") : "" + tableInfo.getLogicDeleteSql(true, true);
        String setSql = sqlSet(tableInfo.isWithLogicDelete(), false, tableInfo, false, "item", "item.");
        String sqlResult = String.format(sql, tableInfo.getTableName(), setSql, tableInfo.getKeyColumn(), "item." + tableInfo.getKeyProperty(), additional);
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sqlResult, modelClass);
        // 第三个参数必须和RootMapper的自定义方法名一致
        return this.addUpdateMappedStatement(mapperClass, modelClass, "updateBatch", sqlSource);
    }

自定义sql注入,MysqlInjector继承DefaultSqlInjector ,代码如下:

import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import java.util.List;

public class MysqlInjector extends DefaultSqlInjector {
    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
        List<AbstractMethod> methods = super.getMethodList(mapperClass,tableInfo);
        // 自定义的insert SQL注入器
        methods.add(new InsertBatchColumn());
        // 自定义的update SQL注入器,参数需要与RootMapper的批量update名称一致
        methods.add(new UpdateBatchColumn("updateBatch"));
        return methods;
    }
}

定义MybatiesPlus的配置文件,将 MysqlInjector 注入进去,代码如下:

import org.jeecg.common.sqlinject.MysqlInjector;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MybatiesPlusConfig {
    @Bean
    public MysqlInjector sqlInjector(){
        return new MysqlInjector();
    }
}

接下来我们还是使用单元测试,构造200000万条数据,当然我们不能一次性插入20万条数据,进行分段插入,代码如下:

public interface TestUserMapper extends RootMapper<TestUser> {
}

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,classes = JeecgSystemApplication.class)
public class UserTest {

    @Autowired
    private TestUserMapper testUserMapper;

    /**
     * 测试自定义批量新增
     */
    @Test
    public void testInsertBatchCustom(){
        List<TestUser> userList = new ArrayList<>();
        int batchSize = 5000; // 每批次插入的数据量
        long s = System.currentTimeMillis();
        for(int i = 0; i < 199999; i++){
            TestUser user = new TestUser();
            user.setName("张三");
            user.setAge("20");
            user.setProvince("重庆市");
            user.setSalary("200000");
            user.setRemark("diitch");
            userList.add(user);
            // 达到批次大小时进行插入
            if(userList.size() == batchSize){
                testUserMapper.insertBatch(userList);
                userList.clear(); // 清空列表,准备下一批数据
            }
        }
// 插入剩余数据
        if(!userList.isEmpty()){
            testUserMapper.insertBatch(userList);
        }
        System.out.println("保存200000条数据消耗" + (System.currentTimeMillis() - s) + "ms");
    }
    }

上面的代码我们设置了一次性批量插入batchSize = 5000,执行结果如下,大概需要4~5秒,batchSize值设置不同,执行效率稍微有点不同:
在这里插入图片描述

2.3 多线程更新 + MP自带saveBatch()方法

上面我们讲了自定义批量插入大概能提升一倍的性能,接下来我们使用多线程方式更新数据,首先我们先测试使用5个线程插入20万条数据,使用Mybaties-plus自带的saveBatch()方法更新,直接上代码:

import org.jeecg.JeecgSystemApplication;
import org.jeecg.modules.demo.test.entity.TestUser;
import org.jeecg.modules.demo.test.mapper.TestUserMapper;
import org.jeecg.modules.demo.test.service.ITestUserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,classes = JeecgSystemApplication.class)
public class UserTest {

    @Autowired
    private ITestUserService userService;

    @Autowired
    private TestUserMapper testUserMapper;

     @Test
    public void testInsertBatchMulThreadSaveBatch() throws Exception{
        int totalRecords = 199999;
        int batchSize = 5000;
        int threadCount = 5; // 可以根据实际情况调整线程数量

        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
        List<Future<Void>> futures = new ArrayList<>();

        long s = System.currentTimeMillis();
        for (int i = 0; i < totalRecords; i += batchSize) {
            int startIndex = i;
            int endIndex = Math.min(i + batchSize, totalRecords);

            List<TestUser> batchList = new ArrayList<>();
            for (int j = startIndex; j < endIndex; j++) {
                TestUser user = new TestUser();
                user.setName("张三");
                user.setAge("20");
                user.setProvince("重庆市");
                user.setSalary("200000");
                user.setRemark("diitch");
                batchList.add(user);
            }

            Future<Void> future = executor.submit(() -> {
                userService.saveBatch(batchList);
                return null;
            });
            futures.add(future);
        }

        // 等待所有线程执行完成
        for (Future<Void> future : futures) {
            future.get();
        }

        executor.shutdown();
        System.out.println("保存200000条数据消耗" + (System.currentTimeMillis() - s) + "ms");
    }
     }

执行结果如下,大概需要3s多:
在这里插入图片描述

2.4 多线程 + 自定义批量插入方法

接下来我们还是使用5个线程来插入数据,只是使用我们自己定义的批量插入方法来插入数据,代码如下:

import org.jeecg.JeecgSystemApplication;
import org.jeecg.modules.demo.test.entity.TestUser;
import org.jeecg.modules.demo.test.mapper.TestUserMapper;
import org.jeecg.modules.demo.test.service.ITestUserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Collectors;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,classes = JeecgSystemApplication.class)
public class UserTest {

    @Autowired
    private ITestUserService userService;

    @Autowired
    private TestUserMapper testUserMapper;

 
    @Test
    public voidtestInsertBatchMulThreadCustom() throws Exception{
        int totalRecords = 199999;
        int batchSize = 5000;
        int threadCount = 5;

        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
        List<Future<Void>> futures = new ArrayList<>();
        Set<String> insertedData = Collections.synchronizedSet(new HashSet<>());

        long s = System.currentTimeMillis();
        for (int i = 0; i < totalRecords; i += batchSize) {
            int startIndex = i;
            int endIndex = Math.min(i + batchSize, totalRecords);

            List<TestUser> batchList = new ArrayList<>();
            for (int j = startIndex; j < endIndex; j++) {
                TestUser user = new TestUser();
                user.setName("张三");
                user.setAge("20");
                user.setProvince("重庆市");
                user.setSalary("200000");
                user.setRemark("diitch");
                batchList.add(user);
            }

            List<TestUser> filteredList = batchList.stream()
                    .filter(user -> !insertedData.contains(user.getName()))
                    .collect(Collectors.toList());

            Future<Void> future = executor.submit(() -> {
                testUserMapper.insertBatch(filteredList);
                filteredList.forEach(user -> insertedData.add(user.getName()));
                return null;
            });
            futures.add(future);
        }

     // 等待所有线程执行完成
        for (Future<Void> future : futures) {
            future.get();
        }

        executor.shutdown();
        System.out.println("保存200000条数据消耗" + (System.currentTimeMillis() - s) + "ms");
    }
}

执行结果如下,大概需要2s左右时间
在这里插入图片描述

三.总结

一般我们设置rewriteBatchedStatements=true时,批量插入功能已经相对较快,如果还满足不了需求,我们可以使用多线程进行批量插入,下面是在设置rewriteBatchedStatements=true时,插入20万条数据saveBatch()以及 saveBatch + 多线程的方式的执行结果:
单独的saveBatch()方法,差不多也是4秒多,也达到了我们自定义的批量插入方法性能:
在这里插入图片描述
saveBatch() + 多线程的方法,执行结果如下,大概只需要1秒多,比我们自定义批量插入 + 多线程方法还要快:

在这里插入图片描述

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

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

相关文章

【Python编程基础6/6】双向选择的判断

目录 知识回顾 导入 if-else 执行顺序 特性 两种判断语句的对比 就近原则 空值 定义 非空 定义 在判断语句中的关系 应用场景 练习 Debug 总结 知识回顾 在上节课中&#xff0c;我们学习了 if 判断&#xff0c;如果布尔表达式成立&#xff0c;就执行后面的代码块…

数据结构(二)——线性表(顺序表)

二、线性表 2.1线性表的定义和基本操作 2.1.1 线性表的基本概念 线性表&#xff1a;是具有相同数据类型的 n 个数据元素的有限序列。(Eg:所有的整数按递增次序排列&#xff0c;不是顺序表&#xff0c;因为所有的整数是无限的)其中n为表长&#xff0c;当n0时线性表是一个空表…

kali当中不同的python版本切换(超简单)

kali当中本身就是自带两个python版本的 配置 update-alternatives --install /usr/bin/python python /usr/bin/python2 100 update-alternatives --install /usr/bin/python python /usr/bin/python3 150 切换版本 update-alternatives --config python 0 1 2编号选择一个即可…

人才推荐 | 高级半导体工艺工程师,美国凯斯西储大学电化学博士

编辑 / 木子 审核 / 朝阳 伟骅英才 伟骅英才致力于以大数据、区块链、AI人工智能等前沿技术打造开放的人力资本生态&#xff0c;用科技解决职业领域问题&#xff0c;提升行业数字化服务水平&#xff0c;提供创新型的产业与人才一体化服务的人力资源解决方案和示范平台&#x…

uniapp 云开发笔记

uniapp云开发官方文档https://uniapp.dcloud.io/uniCloud/learning.html 新建 关联云空间 云函数获取用户openID uniCloud API列表https://uniapp.dcloud.io/uniCloud/cf-functions.html#unicloud-api%E5%88%97%E8%A1%A8 自建云函数login event中包含前端传来的参数 uniCloud.…

LeetCode刷题笔记之两数相加【数组】【中等】

两数相加 刷题笔记 &#x1f565;日期&#xff1a; 2024/03/09 题目描述&#xff1a; 给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同…

mysql 性能优化——磁盘刷脏页性能优化

前言 大家是不是感觉mysql 更新挺快的呀&#xff0c;有没有想过mysql 更新为什么那么快。按道理说&#xff0c;mysql 更新都是先找到这一行数据&#xff0c;然后在去更新。意味着&#xff0c;就有两次磁盘操作&#xff0c;一个是磁盘读&#xff0c;一个是磁盘写。如果真的是这…

使用 SPL 高效实现 Flink SLS Connector 下推

作者&#xff1a;潘伟龙&#xff08;豁朗&#xff09; 背景 日志服务 SLS 是云原生观测与分析平台&#xff0c;为 Log、Metric、Trace 等数据提供大规模、低成本、实时的平台化服务&#xff0c;基于日志服务的便捷的数据接入能力&#xff0c;可以将系统日志、业务日志等接入 …

【鸿蒙开发】第十七章 Web组件(一)

1 Web概述 Web组件用于在应用程序中显示Web页面内容&#xff0c;为开发者提供页面加载、页面交互、页面调试等能力。 页面加载&#xff1a;Web组件提供基础的前端页面加载的能力&#xff0c;包括&#xff1a;加载网络页面、本地页面、html格式文本数据。 页面交互&#xff1a…

Python学习之基础语法

一、HelloWorld 二、Python基础语法 2.1 字面量 定义&#xff1a;在代码中&#xff0c;被写下来的固定的值&#xff0c;称之为字面量。 常用的6种值的类型 字符串 Python中&#xff0c;字符串需要用双引号包围&#xff1b; 被双引号包围的都是字符串 666 13.14 "黑马…

YOLOv3: An Incremental Improvement

新网络是YOLOv2、Darknet-19中使用的网络和那些新奇的残余网络之间的混合方法。我们的网络使用连续的3 3和1 1卷积层&#xff0c;但现在也有一些快捷连接&#xff0c;并且明显更大。它有53个卷积层&#xff0c;所以我们叫它Darknet-53。 这个新网络比Darknet19强大得多&#…

misc40

下载附件&#xff0c;发现只有第三个wav文件需要密码&#xff0c;其他都可以看 打开 conversion.txt 二进制转十进制得到202013 开 一张普通的二维码.png&#xff0c;直接扫不出结果。 010查看图片尾部发现 Brainfuck 编码 解码得到&#xff1a; 和谐民主和谐文明和谐和谐和谐…

WebStorm 开启 eslint 自动格式化配置

之后在 ctrl s保存之后&#xff0c;webstorm 都会根据eslint 的规则自动格式化。

缓存雪崩,穿透,击穿

为什么要设置缓存&#xff1a; 有海量并发的业务场景需要&#xff0c;大量的请求涌入关系型数据库&#xff0c;基于磁盘的IO读取效率低下&#xff0c;常用的mysql数据库不易进行扩展维护&#xff0c;容易造成数据库崩溃&#xff0c;从而相关业务崩溃&#xff0c;系统崩溃。 因此…

【C++初阶】第五站:C/C++内存管理 (匹配使用,干货到位)

前言&#xff1a; 本文知识点&#xff1a; 1. C/C内存分布2. C语言中动态内存管理方式3. C中动态内存管理4. operator new与operator delete函数 5. new和delete的实现原理 &#xff08;干货在此&#xff09; 6. 定位new表达式(placement-new)7. 常见面试题 目录 C/C内…

spring boot 学习

目录 引言&#xff1a; 一、Spring Boot概述 二、Spring Boot的核心特性 1 自动配置 2 起步依赖 3 内嵌容器 4 监控与管理 三、Spring Boot的入门步骤 1 环境安装 2 创建项建 3 编写代码 1 启动类 2 控制器 3服务 4自动装配 5配置属性 6 JPA实体 4 运行与调试…

Linux网络套接字之UDP网络程序

(&#xff61;&#xff65;∀&#xff65;)&#xff89;&#xff9e;嗨&#xff01;你好这里是ky233的主页&#xff1a;这里是ky233的主页&#xff0c;欢迎光临~https://blog.csdn.net/ky233?typeblog 点个关注不迷路⌯▾⌯ 实现一个简单的对话发消息的功能&#xff01; 目录…

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:RotationGesture)

用于触发旋转手势事件&#xff0c;触发旋转手势的最少手指为2指&#xff0c;最大为5指&#xff0c;最小改变度数为1度。 说明&#xff1a; 从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 接口 RotationGesture(value?: …

Oracle SQL优化(读懂执行计划 一)

目录 SQL执行计划的作用示例演示执行计划概念介绍执行计划实例DISPLAY_CURSOR 类型DISPLAY_AWR 类型 指标详解 SQL执行计划的作用 示例演示 执行计划概念介绍 执行计划实例 DISPLAY_CURSOR 类型 DISPLAY_AWR 类型 指标详解

C/C++指针详解

接下来我们来介绍一下什么是指针&#xff1f; 指针其实就是元素存放地址&#xff0c;更加形象的比喻&#xff1a;在酒店中如果你想要去注必须去付费不然不能住&#xff0c;在计算机也同样如此&#xff08;但是不需要付费哦&#xff09;每当我们使用一个变量或其他需要申请空间…