JpaRepository.save()居然调用了2次insert?

先描述一下场景:

我需要对用户实体进行数据鉴权,所以期望的是监听用户的save()方法执行后,同步执行新增数据权限,并且给对应的用户分配该数据权限。

问题来了,在没有做数据权限前,save方法执行正常,加入了监听器之后,save方法就insert了2次,然后就是报主键冲突(因为是同一个对象)。

看一下实现:

第一步,创建用户实例

JpaRepository.save()居然调用了2次insert?_第1张图片

第二步,创建监听器

JpaRepository.save()居然调用了2次insert?_第2张图片 

第三步,User对象注册监听器

JpaRepository.save()居然调用了2次insert?_第3张图片 

第四步,开始测试

 JpaRepository.save()居然调用了2次insert?_第4张图片

可以看到,一模一样的数据被insert了2次。然后跟到源码查了一下原因,我大概总结一下:

由于JPA的一级缓存是根据session来进行缓存的,而且因为我所有的保存方法都是调用的save()方法,这个方法在你调用的时候并不会直接执行SQL,而是先缓存在一个执行队列。我的创建用户方法调用完成后,执行队列开始执行,然后执行第一条SQL,创建用户,执行完成之后,触发了用户创建后的监听器,监听器就开始保存数据权限和权限分配,这个时候也要操作数据库,JPA的一级缓存机制的从session中拿到了之前缓存的执行队列,然后把你监听器里面要执行的SQL给加了进去!!!然后重新执行队列!!!  问题就在这,我第一条创建用户的SQL已经执行了呀??你从缓存再拿一次队列,然后加入进来,我用户又被insert了一次。

发一下证明图:

 第一步,方法执行完成(还未开始insert)

JpaRepository.save()居然调用了2次insert?_第5张图片

 

 第二步,查看执行队列

JpaRepository.save()居然调用了2次insert?_第6张图片

 第三步,监听器被触发

JpaRepository.save()居然调用了2次insert?_第7张图片

第四步,session缓存被刷新

 JpaRepository.save()居然调用了2次insert?_第8张图片

 好!干的漂亮!

刚执行完的插入用户语句又特么回来了,让我再执行一次!

报错了,主键冲突....

怎么解决?

我的解决方案仅限于我当前的场景:

第一种、不通过监听器实现,手动在创建的地方执行创建数据权限

JpaRepository.save()居然调用了2次insert?_第9张图片

        缺点很明显,如果你将来还有别的域对象要做数据权限,全都得这么搞,每个创建方法都要做这个事情 

第二种、不用同步,用异步

        我这个场景不适用,我需要将创建用户和创建数据权限放在一个事务,一荣俱荣,一损俱损!异步就没法保证事务,当然有的小伙伴可以说用最终一致性解决方案什么的。。。你要想这么玩也行,但是我这得要求实时同步数据。

第三种、把这个数据权限的创建放在save()方法执行前

JpaRepository.save()居然调用了2次insert?_第10张图片

        但是这么玩有个坑! 那就是如果你的实例对象的主键是通过策略实现的,那你拿不到主键ID。当然,这个是说你得先要用这个主键,如果不用就不用管了,如果你要用,你就必须把主键的策略去除,手动设置主键,就是让这个Object传过来的时候,主键已经放进去了。

       

        问题已经描述完了,如果有其他更好解决方案的小伙伴,欢迎留言或私信,期待你的点赞! 

 

你可能感兴趣的:(JAVA,数据库,java,数据库,sql)