一次事务相关的踩坑之旅@Modifying(clearAutomatically = true)

在处理多线程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,可确保动作提交,暂未实际验证,后续补充。

望交流指正。

你可能感兴趣的:(事务)