在处理多线程 save po 引起的数据覆盖时,将 save 改为了 update,如下:
遇到第一个诡异问题,修改完这个,我之前的两张表 InputPO 和 ProgramPO 的关联出现了问题(一对多),数据库中显示,ProgramPO 中的外键 inputId 为 null 了,百撕不得骑姐啊,一个操作影响到了完全不相关的两张表。于是开启了 hibernate 的语句执行日志,对比了一下修改前后的 sql 执行,发现了问题。之前外键正常时,日志能看到这句:
Hibernate:
/* create one-to-many row com.suma.xianrd.sirius.pojo.task.InputPO.programPOs */ update
program
set
inputId=?
where
id=?
明显是建外键的,而用了 update 方法后,这句执行就没了。没道理啊,表之间又没关系。忽然想到了一问题,这些执行的最外层,我加了一个事务,会不会有关系呢?做了如下实验操作一:一个外层带事务的方法,先分别存储了一对关联的 input 和 programpo,然后等 10s,方法结束。现象一:日志看到顺序如下,插入输入 1 -> 插入节目 1 -> 插入输入 2 -> 插入节目 2,10s 后,依次看到两条 update 外键的日志执行。操作二:将上述方法事务取消现象二:日志顺序如下,插入输入 1 -> 插入节目 1 ->update 外键 1 -> 插入输入 2 -> 插入节目 2 ->update 外键 2。操作三:还是上述方法,方法带上事务,等待 10s 后抛异常。现象三:只能看到插入的日志,看不到 update 外键的日志。没有任何输入存入数据库。结论一:事务提交时,才会执行缓存的关联外键操作。操作四:引入我们的主角,update 修改 DeviceAuthPO 的方法,在上述方法插入数据后调用。现象四:在没有异常的情况下,update 外键的日志凭空不见了。
操作到这里,进行相关搜索后,把注意力放在了 @Modifying(clearAutomatically = true) 这个注释上,有注释:会清理缓存,导致同事务内的其它表外键操作丢失;无注释:前面的数据,后面仍然查不到。。源码中这个注释是这样描述的:Defines whether we should clear the underlying persistence context after executing the modifying query. 我们在 update 的方法中都默认将这个配置为 true 了,为的是能让 update 及时生效到数据库。但看这里的描述,这样配置会清掉持久层的上下文,我们看到的现象就是外键操作丢失。
总结下这次跟 jpa 以及事务相关的收获 1、@Modifying(clearAutomatically = true) 注释的 update 方法,会导致本事务内的生成关联表外键的操作丢失 2、同一个事务内多次查询同一条数据,实际语句只会执行一次(事务内出现了带 @Modifying(clearAutomatically = true) 的语句,下一次会重新查)
所以,尽量避免一个事务所包含的动作过多,不利于事务控制,如果此时再有多线程进行处理,出问题概率很高。
后续更新:在 stackoverflow 上看到一篇帖子,描述一样的问题(clearAutomatically = true 导致部分数据库动作丢失),原文这样描述的:This approach clears the persistence context not to have outdated values, but it drops all non-flushed changes still pending in the EntityManager. 清缓存的同时会把未提交的修改给扔掉?和自己测试的结果是一致的。这个动作类似于,在没有提交的情况下,执行了一次 entityManager.clear()。新版版 spring data jpa 引入了另外一个 flushAutomatically = true,可确保动作提交,暂未实际验证,后续补充。
望交流指正。