mybatis plus很好,但是我被它坑了!
作者今天在开发一个后台发送消息的功能时,由于需要给多个用户发送消息,于是使用了 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() 方法用于保存通知消息以及用户通知记录。执行逻辑如下,
[*]保存通知消息
[*]根据用户 id,组装用户通知记录集合,返回 200 条用户通知记录
[*]批量保存用户通知记录集合
前两步骤耗时都很少,我们直接看第三步操作耗时,结合 sql 执行日志,如下,
-- slow sql 5542 millis. INSERT INTO oa_notify_record( notifyId, receiveUserId, receiveUserName, isRead,createTime )VALUES( ?, ?, ?, ?,? )
5681再结合 mybatis free log 插件打印完整 sql 如下图,
https://p26-sign.toutiaoimg.com/tos-cn-i-axegupay5k/67abb67bdc6945e2b27f2010caf3ad51~noop.image?_iz=58558&from=article.pc_detail&x-expires=1699327992&x-signature=a6W%2BWMlTIXA9kNQXLka5ji6eZNY%3D
可以看出,我们批量保存用户通知记录是一条一条保存得,已经可以猜测就是批量插入方法导致耗时较高。
这里使用 mybatis log free 插件,它可以自动帮我们在控制台打印完整得 mybatis sql 语句。有需要可以在 idea 插件中心搜索 mybatis log free 下载安装。
结合 saveBatch() 底层源码也能够看出,mybatis plus 对于批量操作是在 executeBatch() 方法内使用 for 循环执行插入操作得,源码如下图,
https://p3-sign.toutiaoimg.com/tos-cn-i-6w9my0ksvp/28778c7fb3204e38822d2cfae194e3da~noop.image?_iz=58558&from=article.pc_detail&x-expires=1699327992&x-signature=hBeNAQL5IAuGDtgJFRoN2qa79NE%3D
https://p3-sign.toutiaoimg.com/tos-cn-i-6w9my0ksvp/e9771f0a5f654a9d8cca72791aab950f~noop.image?_iz=58558&from=article.pc_detail&x-expires=1699327992&x-signature=ZBbHa59fjGDnK07dLw%2FvLFfNb0s%3D
到这里我们应该也能猜出了在测试环境执行较快得原因,因为在测试环境需要批量保存得用户通知记录比较少,只有几条记录,所以很快。但是上预发布后,由于预发布中需要批量保存得用户通知记录比较多达到了数百条,所以执行较慢,耗时达到了 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; imybatis -> jdbc 这一条批量操作链路,但是其实我们还需要在 jdbcurl 上添加一个 rewriteBatchedStatements=true 参数即可解决这个问题。</p>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 上添加了参数后,看看效果,如下图,
https://p3-sign.toutiaoimg.com/tos-cn-i-6w9my0ksvp/e586f6effd3547489cc71c610de9792b~noop.image?_iz=58558&from=article.pc_detail&x-expires=1699327992&x-signature=gVpFVXgdZsXr%2FMznrsTgo2BmZ34%3D
可以看到 jdbcurl 添加了 rewriteBatchedStatements=true 参数后,批量操作的执行耗时已经只有 200 毫秒,自此也就解决了 mybatis plus 提供的 saveBatch() 方法执行耗时较高得问题。
总结
mybatis plus 给开发人员带来了很多便利,但是其中也有一些坑点,比如上文所提到得批量操作耗时问题,如果不注意的话,就有可能调入坑里,各位开发同学可以检查自己或者公司项目中 jdbcurl 是否缺失 rewriteBatchedStatements=true 参数,加以改正,避免重复掉入这个坑里。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页:
[1]