级联保存时导致 JpaAuditing(审计)的时间字段为空

95 阅读1分钟

级联保存时导致 JpaAuditing(审计)的时间字段为空

  • Org.class
@Getter
@Setter
@Accessors(chain = true)
@Entity
@Table(name = "org")
@EntityListeners(value = AuditingEntityListener.class)
public class Org extends DomsBaseEntity {
​
    public static final String RESOURCE_KEY = "org";
​
    /**
     * 组织机构名
     */
    @Column(name = "name")
    private String name;
  
    // ... 省略其他字段
​
    /**
     * 创建时间
     */
    @CreatedDate
    private LocalDateTime createdDate;
​
    /**
     * 修改时间
     */
    @LastModifiedDate
    private LocalDateTime lastModifiedDate;
​
    /**
     * 父组织机构,此值为 -1 表示顶层组织机构
     */
    @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
    @JoinColumn(name = "parent_org_id")
    private Org parentOrg;
​
    @OneToMany(mappedBy = "parentOrg", orphanRemoval = true)
    private Set<Org> children = new LinkedHashSet<>();
}
  • DomsBaseEntity
@Data
@MappedSuperclass
public class DomsBaseEntity implements Serializable {
    @Id
    private String id = ObjectIdGenerator.next();
}
  • 测试类
@Test
public void test() {
​
  List<Org> list = new ArrayList<>();
  Org parent = new Org().setName("parent");
  Org child = new Org().setName("child");
​
  child.setParentOrg(parent);
  list.add(parent);
  list.add(child);
​
  List<Org> orgs = orgRepo.saveAll(list);
}

Org 实体加入了审计功能,按照预期应该每次保存或更新,创建时间(createdDate)和更新时间(lastModifiedDate)字段都会自动更新的,但上面测试类的结果是 parent 机构的创建时间字段为空。

image-20221025235805615

image-20221025235902934

  • 在调用 orgRepo.saveAll(list) 前,list 里的两个 Org 对象的时间字段都是空的(注意这里的 parentOrg 里的也是空的)
  • 调用链路:SimpleJpaRepository#saveAll() => SimpleJpaRepository#save()

image-20221026001316424

  • 由于每个 entity 在保存前都是手动设置了 id,因此保存实际上走的都是更新操作(merge)

image-20221026002334503

  • 第一个保存的 parent,此时通过审计功能已经自动生成了时间,但是它返回的对象并不是原先传入的 entity 了。

image-20221026002629603

  • 保存第二个实体 child 时,entity 关联的 parentOrg 的时间字段没有更新,就还是空的
  • 此时保存 child,由于 child 是第一次保存,时间字段被自动赋值

image-20221026004210867

  • 后续再级联更新 child 的 parentOrg,由于更新操作审计 只会修改lastModifiedDate字段,而不会对createdDate做修改,因此createdDate字段就被传的null值覆盖掉了。
  • 最终导致createdDate字段为空

解决

  1. 手动设置时间,但这样也完全失去了的审计的作用

    parent.setCreatedDate(LocalDateTime.now());
    parent.setLastModifiedDate(LocalDateTime.now());
    
  2. 关闭级联保存、更新

    @ManyToOne(cascade = {/*CascadeType.PERSIST, CascadeType.MERGE,*/ CascadeType.REFRESH})
    @JoinColumn(name = "parent_org_id")
    private Org parentOrg;