首页 > 其他分享 >mybatis plus很好,但是我被它坑了!

mybatis plus很好,但是我被它坑了!

时间:2023-10-31 12:04:28浏览次数:32  
标签:批量 plus notify sql mybatis true 但是

作者今天在开发一个后台发送消息的功能时,由于需要给多个用户发送消息,于是使用了 mybatis plus 提供的 saveBatch() 方法,在测试环境测试通过上预发布后,测试反应发送消息接口很慢得等 5、6 秒,于是我就登录预发布环境查看执行日志,发现是 mybatis plus 提供的 saveBatch() 方法执行很慢导致,于是也就有了本篇文章。

mybatis plus 是一个流行的 ORM 框架,它基于 mybatis,提供了很多便利的功能,比如代码生成器、通用 CRUD、分页插件、乐观锁插件等。它可以让我们更方便地操作数据库,减少重复的代码,提高开发效率。

注意:本文所使用的 mybatis plus 版本是 3.5.2 版本。

案发现场还原

/**
 * 先保存通知消息,在批量保存用户通知记录
 */
@Transactional(rollbackFor = Exception.class)
@Override
public boolean saveNotice(Notify notify, String receiveUserIds) {
    long begin = System.currentTimeMillis();
    notify.setCreateTime(new Date());
    notify.setCreateBy(ShiroUtil.getSessionUid());
    if (notify.getPublishTime() == null) {
        notify.setPublishTime(new Date());
    }
    boolean insert = save(notify);
    List<NotifyRecord> collect = new ArrayList<>();
    List<String> receiveUserList = fillNotifyRecordList(notify, receiveUserIds, collect);
    notifyRecordService.saveBatch(collect);
    long end = System.currentTimeMillis();
    System.out.println(end - begin);
    ...
    return insert;
}

/**
 * 根据用户id,组装用户通知记录集合,返回200条记录
 */
public List<String> fillNotifyRecordList(Notify notify, String receiveUserIds, List<NotifyRecord> collect) {
    List<String> noticeRecordList = new ArrayList<>(200);
    ...
    // 组将两百条用户通知记录
    return noticeRecordList;
}

如上代码,我有一个 saveNotice() 方法用于保存通知消息以及用户通知记录。执行逻辑如下,

  1. 保存通知消息
  2. 根据用户 id,组装用户通知记录集合,返回 200 条用户通知记录
  3. 批量保存用户通知记录集合

前两步骤耗时都很少,我们直接看第三步操作耗时,结合 sql 执行日志,如下,

-- slow sql 5542 millis. INSERT INTO oa_notify_record  ( notifyId, receiveUserId, receiveUserName, isRead,  createTime )  VALUES  ( ?, ?, ?, ?,  ? )[225,"fcd90fe3990e505d07c90a238f75e9c1","niuwawa",false,"2023-10-30 23:54:04"]
5681

再结合 mybatis free log 插件打印完整 sql 如下图,

 

可以看出,我们批量保存用户通知记录是一条一条保存得,已经可以猜测就是批量插入方法导致耗时较高。

这里使用 mybatis log free 插件,它可以自动帮我们在控制台打印完整得 mybatis sql 语句。有需要可以在 idea 插件中心搜索 mybatis log free 下载安装。

结合 saveBatch() 底层源码也能够看出,mybatis plus 对于批量操作是在 executeBatch() 方法内使用 for 循环执行插入操作得,源码如下图,

 

 

到这里我们应该也能猜出了在测试环境执行较快得原因,因为在测试环境需要批量保存得用户通知记录比较少,只有几条记录,所以很快。但是上预发布后,由于预发布中需要批量保存得用户通知记录比较多达到了数百条,所以执行较慢,耗时达到了 5、6 秒之久。

由上述源码可以看出,mybatis plus 的批量操作底层使用的还是 mybatis 提供的 batch 模式实现批量插入以及更新的。而 mybatis 提供的 batch 模式操作底层使用的还是 jdbc 驱动提供的批量操作模式,jdbc 批量操作示例代码如下,

public static void main(String[] args) {
    Connection conn = null;
    PreparedStatement statement = null;
    try {
        // 数据库连接
        String url = "jdbc:mysql://*************?autoReconnect=true&nullCatalogMeansCurrent=true&failOverReadOnly=false&useUnicode=true&characterEncoding=UTF-8";
        String user = "******";
        String password = "************";
        // 添加批处理参数
//            url = url + "&rewriteBatchedStatements=true";
        // 加载驱动类
        Class.forName("com.mysql.cj.jdbc.Driver");
        // 创建连接
        conn = DriverManager.getConnection(url, user, password);
        // 创建预编译 sql 对象
        statement = conn.prepareStatement("UPDATE table_test_demo set code = ? where id = ?");
        long a = System.currentTimeMillis(); // 计时
        // 这里添加 100 个批处理参数
        for (int i = 1; i <= 100; i++) {
            statement.setString(1, "测试1");
            statement.setInt(2, i);
            statement.addBatch(); // 批量添加
        }

        long b = System.currentTimeMillis(); // 计时
        System.out.println("添加参数耗时:" + (b-a)); // 计时

        int[] r = statement.executeBatch(); // 批量提交
        statement.clearBatch(); // 清空批量添加的 sql 命令列表缓存

        long c = System.currentTimeMillis(); // 计时
        System.out.println("执行sql耗时:" + (c-b)); // 计时
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        // 主动释放资源
        try {
            if (statement != null) {
                statement.close();
            }
            if (conn != null) {
                conn.close();
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }
}
  • statement.addBatch() 将 sql 语句打包到一个容器中
  • statement.executeBatch() 将容器中的 sql 语句提交
  • statement.clearBatch() 清空容器,为下一次打包做准备

推荐博主开源的 H5 商城项目waynboot-mall,这是一套全部开源的微商城项目,包含三个项目:运营后台、H5 商城前台和服务端接口。实现了商城所需的首页展示、商品分类、商品详情、商品 sku、分词搜索、购物车、结算下单、支付宝/微信支付、收单评论以及完善的后台管理等一系列功能。 技术上基于最新得 Springboot3.0、jdk17,整合了 MySql、Redis、RabbitMQ、ElasticSearch 等常用中间件。分模块设计、简洁易维护,欢迎大家点个 star、关注博主。

github 地址:https://github.com/wayn111/waynboot-mall

那么问题出现在哪里了?明明已经使用了批量操作,但耗时还是很慢,别急,跟着我往下看。

解决方法

到这里,也就是本文得重点所在了,那怎么解决这个问题嘞?如何既利用 mybatis plus 提供得便携性,也能够解决批量操作耗时较高得问题。

虽然我们使用了 mybatis plus -> mybatis -> jdbc 这一条批量操作链路,但是其实我们还需要在 jdbcurl 上添加一个 rewriteBatchedStatements=true 参数即可解决这个问题。

MySQL 的 JDBC 连接的 url 中要加 rewriteBatchedStatements 参数,并保证 5.1.13 以上版本的驱动,才能实现高性能的批量插入。

MySQL JDBC 驱动在默认情况下会无视 executeBatch()语句,把我们期望批量执行的一组 sql 语句拆散,一条一条地发给 MySQL 数据库,批量插入实际上是单条插入,直接造成较低的性能。只有把 rewriteBatchedStatements 参数置为 true, 驱动才会帮你批量执行 SQL。另外这个选项对 INSERT/UPDATE/DELETE 都有效。

rewriteBatchedStatements=true 的意思是,当你在 Java 程序中使用批量插入/修改/删除(batching)时,MySQL JDBC 驱动程序将尝试重新编写(rewrite)你的 SQL 语句,以便更有效地执行这些批量插入操作。

OK,在我们给 jdbcurl 上添加了参数后,看看效果,如下图,

 

可以看到 jdbcurl 添加了 rewriteBatchedStatements=true 参数后,批量操作的执行耗时已经只有 200 毫秒,自此也就解决了 mybatis plus 提供的 saveBatch() 方法执行耗时较高得问题。

总结

mybatis plus 给开发人员带来了很多便利,但是其中也有一些坑点,比如上文所提到得批量操作耗时问题,如果不注意的话,就有可能调入坑里,各位开发同学可以检查自己或者公司项目中 jdbcurl 是否缺失 rewriteBatchedStatements=true 参数,加以改正,避免重复掉入这个坑里。

标签:批量,plus,notify,sql,mybatis,true,但是
From: https://www.cnblogs.com/waynaqua/p/17799918.html

相关文章

  • 2023-10-21:用go语言,一共有三个服务A、B、C,网络延时分别为a、b、c 并且一定有:1 <= a <= b
    2023-10-21:用go语言,一共有三个服务A、B、C,网络延时分别为a、b、c并且一定有:1<=a<=b<=c<=10^9但是具体的延时数字丢失了,只有单次调用的时间一次调用不可能重复使用相同的服务,一次调用可能使用了三个服务中的某1个、某2个或者全部3个服务比如一个调用的时间,T=100100的延时......
  • Element Plus el-tree懒加载默认选中
    百度上试了很多方法,设置default-expanded-keys不生效,最后使用了下面的方法,亲测有效constloadNode=async(node:Node,resolve:(data:AreaType[])=>void)=>{if(node.level===0){const{data}=awaitgetRegionList(areaOptions)if(!props.special)......
  • Mybatis sql日志在线转换工具
     Mybatissql日志在线转换工具 有时候在linux服务器上复制出来的SQL,带问号,不好转化成正常的SQL,网上搜了一下也没发现有在线的转换工具,刚好自己有个轻语音乐网站,有域名和服务器,所以想着把这个页面集成上去,也方便自己日常使用。 Mybatissql日志在线转换工具地址:http://linl......
  • myBatis
    myBatismyBatis框架也被称之为OBM(面向对象映射)框架。ORMjiu就是一种解决面向对象与关系型数据库中数据不匹配的技术,它通过藐视java对象与数据库之间的映射关系,自动将java应用程序中的对象看持久化到关系型数据库的表中持久化与持久化层持久化:就是把数据保存到可掉电式岑楚设......
  • 解决使用mitmprox抓包可以访问网页,但是使用python request 调用该网站接口报错问题
    可能有几种原因导致这种情况。以下是一些常见的问题和可能的解决方法:证书验证问题:当你使用mitmproxy抓包时,它通常会生成自签名的SSL证书,以便进行中间人攻击检查。但在Python中使用requests库时,默认情况下,它会验证SSL证书的有效性。你需要禁用SSL验证,以便使用mitmproxy生成的证书......
  • 关于学习Mybatis-plus的认识
    1.实体类的类名和属性尽量一致,如果不一致需要用注解进行指定。2.mybatis-plus是把实体类的类名直接转换成小写到数据库查找,所以需要@TableName(value="表名")来指定表的名字进行查询@TableName("sys_user")publicclassUser{privateLongid;privateStringn......
  • 2021 CCPC桂林 B.A Plus B Problem (线段树)
    传送门线段树大模拟!。考验线段树功底的时候来了,作为队伍的史山选手,写这么史也是情有可原的。#include<bits/stdc++.h>usingll=longlong;constintINF=0x3f3f3f3f;constintN=1e6+10;typedefstd::pair<int,int>PII;#definelsu<<1#definersu<<1|......
  • Springboot+Mybatis+Mybatisplus 框架中增加自定义分页插件和sql 占位符修改插件
    一、Springboot简介springboot是当下最流行的web框架,SpringBoot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置,让这些框架集成在一起变得更加简单,简化了我......
  • ?Mybatis多表查询(1:1、1:N、N:N),MP多表查询(自定义SQL)
    Mybatis多表关联查询Gitee地址:https://gitee.com/zhang-zhixi/mybatis-tables-query.git数据表:oracleCREATETABLE"T_ORDER"("ID"NUMBERNOTNULL,"F_ORDER_TIME"DATE,"F_TOTAL"VARCHAR2(255BYTE),"F_USER_ID"NU......
  • 20版本Camplus控制器与其它版本的差异
    目录主界面隧道模式链路模板零配置开局无线认证LAN-WAN互联创建VN布局接口路由站点上网主界面如下所示:主界面整体为深蓝偏紫的颜色隧道模式如下所示,打开“EVPN”之后,在主界面上就会显示出“多分支互联”,SD-WAN的相关配置在按钮下进行配置。链路模板链路模板会提供两种接口服务,如下......