spring data jpa中实现数据库记录的insertIfNotExists

背景

最近在业务开发中,需要通过lazy init的方式向一张table中插入数据,该table中一个column存在unique约束。

方案探索

通过catch exception

原始版本中,采用如下pseudo code所示的方式进行,其中key为unqiue id

    Record insertIfNotExists(K key) {

        Record record = recordRepo.findByKey(key)
        if (record == null) {
            Record newRecord = new Record()
            try {
                recordRepo.save(newRecord)
            } catch (DataIntegrityViolationException e) {
                //already exists
                return recordRepo.findByKey(key)
            }

        } else {
            return record
        }
    }

在不对方法添加transaction的情况下,运行正常,但在transaction中执行时,会产生如下异常:

org.hibernate.AssertionFailure: null id in entry (don't flush the Session after an exception occurs)

原因在这里:

mysql - org.hibernate.AssertionFailure: null id in entry (don't flush the Session after an exception occurs) - Stack Overflow

session中已经持有了一个未持久化的entity,该entity的id为null。

通过insert on duplicate update

该语法的说明可以参考mysql manual:

MySQL :: MySQL 5.7 Reference Manual :: 13.2.5.2 INSERT ... ON DUPLICATE KEY UPDATE Statement

遵循的是insert -> if exists -> update current的流程。

 pseudo code如下:

    Record insertIfNotExists(K key) {
        Record record = recordRepo.findByKey(key)
        if (record == null) {
            recordRepo.insertOnDuplicateUpdate(key)
            return record
        }
        return recordRepo.findByKey(key)
    }

需要注意,该function必须在一个transaction中,否则抛出异常。

该语句返回值为整数,代表了实际操作row的数量,文档中的描述是

With ON DUPLICATE KEY UPDATE, the affected-rows value per row is 1 if the row is inserted as a new row, 2 if an existing row is updated, and 0 if an existing row is set to its current values. If you specify the CLIENT_FOUND_ROWS flag to the mysql_real_connect() C API function when connecting to mysqld, the affected-rows value is 1 (not 0) if an existing row is set to its current values.

实际情况下,spring data jpa的 CLIENT_FOUND_ROWS为true,所以该sql的返回值为1或2。1代表插入一行新纪录或保持不变,2代表更新了原有纪录。

绝大多数情况下,该flag设置为true,保证了其它依赖affected rows & found rows的返回正常。

对于mysql客户端,可以通过options设置,以jetbrains的datagrip为例,设置方法可参考:

https://youtrack.jetbrains.com/issue/DBE-9085

结论

在有transaction的情况下,通过insert on duplicate update实现更稳定和优雅,但需要注意返回值的问题。可以通过增加一个值为random的column,在update clause中更新该column值,即可通过返回值判断是否进行了row insert。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值