级联保存时导致 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 机构的创建时间字段为空。
- 在调用 orgRepo.saveAll(list) 前,list 里的两个 Org 对象的时间字段都是空的(注意这里的 parentOrg 里的也是空的)
- 调用链路:SimpleJpaRepository#saveAll() => SimpleJpaRepository#save()
- 由于每个 entity 在保存前都是手动设置了 id,因此保存实际上走的都是更新操作(merge)
- 第一个保存的 parent,此时通过审计功能已经自动生成了时间,但是它返回的对象并不是原先传入的 entity 了。
- 保存第二个实体 child 时,entity 关联的 parentOrg 的时间字段没有更新,就还是空的
- 此时保存 child,由于 child 是第一次保存,时间字段被自动赋值
- 后续再级联更新 child 的 parentOrg,由于更新操作
审计只会修改lastModifiedDate字段,而不会对createdDate做修改,因此createdDate字段就被传的null值覆盖掉了。 - 最终导致
createdDate字段为空
解决
-
手动设置时间,但这样也完全失去了的
审计的作用parent.setCreatedDate(LocalDateTime.now()); parent.setLastModifiedDate(LocalDateTime.now()); -
关闭级联保存、更新
@ManyToOne(cascade = {/*CascadeType.PERSIST, CascadeType.MERGE,*/ CascadeType.REFRESH}) @JoinColumn(name = "parent_org_id") private Org parentOrg;