如何避免JPA中@ManyToOne关联实体更新引发审计表冗余?
解决JPA保存关联实体时触发冗余更新与审计条目的问题
问题背景
现有两个JPA实体TariffOption和DictTariff,双向关联配置如下:
TariffOption实体:
@Data @NoArgsConstructor @Entity @Audited @AuditTable(schema = "audit", value = "tariff_option") @AuditOverride(forClass = BaseEntity.class, isAudited = true) @Table(name = "tariff_option") @EqualsAndHashCode(of = {}, callSuper = true) @ToString() public class TariffOption extends BaseEntity { private static final long serialVersionUID = -6398231779406280786L; ... @ManyToOne @JoinColumn(name = "dict_tariff_id", updatable = false) private DictTariff tariff; }
DictTariff实体:
@Data @NoArgsConstructor @Entity @Audited @AuditTable(schema = "audit", value = "dict_tariff") @AuditOverride(forClass = BaseEntity.class, isAudited = true) @Table(name = "dict_tariff") @EqualsAndHashCode(of = {}, callSuper = true) @ToString(exclude = {"contractorTypes", "service", "tariffOptions", "dictTariffOptions"}) @JsonIgnoreProperties(value = {"contractorTypes", "service", "tariffOptions", "dictTariffOptions"}) public class DictTariff extends BaseEntity { private static final long serialVersionUID = -3881580795280130829L; ... @OneToMany(mappedBy = "tariff", fetch = FetchType.LAZY) private List<TariffOption> tariffOptions; }
调用repository.save(tariffOption)保存TariffOption时,关联的DictTariff即使属性无变化,也会被重复保存,进而在Envers审计表中生成冗余条目。尝试过EntityManager.detach(dictTariff)和重新获取实体的方法,均无法阻止该问题。
解决方案
1. 标记关联实体为只读
在保存TariffOption前,将关联的DictTariff设置为只读状态,Hibernate会停止跟踪该实体的状态变化,从而避免不必要的更新操作:
DictTariff tariff = tariffOption.getTariff(); entityManager.setReadOnly(tariff, true); tariffOptionRepository.save(tariffOption);
2. 调整@ManyToOne的级联策略
由于DictTariff属于字典类实体,通常为预先存在的数据,无需在保存TariffOption时级联操作关联实体。明确设置空级联策略,切断级联触发的保存逻辑:
@ManyToOne(cascade = {}, fetch = FetchType.LAZY) @JoinColumn(name = "dict_tariff_id", updatable = false) private DictTariff tariff;
结合已有的updatable=false配置,完全避免TariffOption保存时对DictTariff的不必要操作。
3. 使用代理对象关联实体
不直接使用已加载的完整DictTariff实体,而是通过ID获取Hibernate代理对象。代理仅包含ID属性,不会被检测到脏数据变化,因此不会触发更新:
Long tariffId = ...; // 获取目标DictTariff的ID DictTariff tariffProxy = entityManager.getReference(DictTariff.class, tariffId); tariffOption.setTariff(tariffProxy); tariffOptionRepository.save(tariffOption);
4. 配置Envers忽略无变化审计条目(仅针对审计优化)
如果无法阻止DictTariff的更新操作,但希望避免审计表生成冗余条目,可以启用Envers的修改标记功能,仅当实体属性真正变化时生成审计记录:
- 在
DictTariff类上添加@Audited(withModifiedFlag = true):
@Audited(withModifiedFlag = true) public class DictTariff extends BaseEntity { // ... 原有代码 }
- 在JPA配置中添加Envers参数:
jpa: generate-ddl: false hibernate: ddl-auto: validate properties: hibernate: dialect: rf.dom.billing.model.common.postgres.CustomPostgreSqlDialect enable_lazy_load_no_trans: true org: hibernate: envers: default_schema: audit track_entities_changed_in_revision: true
注意:该方案仅优化审计条目,无法阻止
DictTariff的更新操作,适用于无法彻底阻止更新的场景。
内容的提问来源于stack exchange,提问作者Eljah
相关产品推荐
相关产品推荐

