Spring Boot中@PrePersist与@CreatedBy引发共享引用错误
问题场景与报错
我有两个双向一对多关联的JPA实体,同时继承了带JPA审计注解的基类,在Spring Boot 2.3.4.RELEASE版本下,更新子实体AutomatedRefundLedger时调用save方法抛出集合共享引用异常,具体代码与报错如下:
实体代码
RefundInitiate(父实体)
@Data @Accessors(chain = true) @Builder @Entity @FieldDefaults(level = AccessLevel.PRIVATE) @AllArgsConstructor @NoArgsConstructor @Table(name = "refund_initiates") public class RefundInitiate extends BaseEntityV3 { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) Integer id; // ... 其他字段 @OneToMany(mappedBy = "refundInitiate", cascade = CascadeType.ALL, orphanRemoval = true) List<AutomatedRefundLedger> refundLedgers = new ArrayList<>(); }
AutomatedRefundLedger(子实体)
@Data @Accessors(chain = true) @AllArgsConstructor @NoArgsConstructor @Table(name = "refund_ledger") @Entity @Builder public class AutomatedRefundLedger extends BaseEntityV3 { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) Integer id; // ... 其他字段 @ManyToOne @JoinColumn(name = "refund_initiate_id", nullable = false) RefundInitiate refundInitiate; }
审计基类BaseEntityV3
@Getter @Setter @ToString @FieldDefaults(level = AccessLevel.PRIVATE) @AllArgsConstructor @NoArgsConstructor @MappedSuperclass @EntityListeners(AuditingEntityListener.class) public class BaseEntityV3 { @CreatedBy @Column(name = "created_by") Integer createdBy; @CreatedDate @Column(name = "created_on") Date createdOn; @LastModifiedBy @Column(name = "updated_by") Integer updatedBy; @LastModifiedDate @Column(name = "updated_on") Date updatedOn; @Column(name = "is_deleted") Boolean deleted = Boolean.FALSE; }
审计配置类
@Configuration @EnableJpaAuditing(auditorAwareRef = "userIdAuditorProvider") public class AuditorAwareConfig { @Bean(name = "userIdAuditorProvider") public AuditorAware<Integer> userIdAuditorProvider() { return new AuditorAwareImpl(); } } @Component class AuditorAwareImpl implements AuditorAware<Integer> { @Autowired EmployeeRepository employeeRepo; @Override public Optional<Integer> getCurrentAuditor() { try { final Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); return Optional.of((principal instanceof UserDetails) ? this.getEmployeeId(((UserDetails) principal).getUsername()) : 1); } catch (Exception e) { return Optional.of(1); } } private Integer getEmployeeId(String username) { // TODO: 移除测试代码 return employeeRepo.findFirstByUsername(username).map(Employee::getId).orElse(1); } }
报错信息
[Request processing failed; nested exception is org.springframework.orm.jpa.JpaSystemException: Found shared references to a collection: payments.module.data.model.RefundInitiate.refundLedgers; nested exception is org.hibernate.HibernateException: Found shared references to a collection: payments.module.data.model.RefundInitiate.refundLedgers] with root cause org.hibernate.HibernateException: Found shared references to a collection: payments.module.data.model.RefundInitiate.refundLedgers
排查情况
- 移除
@CreatedBy等JPA审计注解、取消继承BaseEntityV3,或删除RefundInitiate中的refundLedgers字段后,更新操作可正常执行 - 手动用
@PrePersist/@PreUpdate实现审计也会触发相同错误 - Spring Boot新版本(高于2.3.x)中无此问题
- 未显式更新
refundLedgers集合,不属于常规的集合共享引用场景
原因分析
这是Spring Boot 2.3.x版本(对应Hibernate 5.4.x系列)与JPA审计机制结合时的兼容性问题:当审计监听器触发子实体的更新审计时,会自动加载关联的父实体及其集合字段,而Hibernate的集合引用检查机制在这种隐式加载场景下,误将初始化的集合实例判定为被多个实体共享,从而抛出异常。
解决方案
1. 升级Spring Boot版本(推荐)
直接升级到Spring Boot 2.4及以上版本,官方已修复该审计与双向关联集合的兼容性问题,这是最彻底的解决方式。
2. 修正集合初始化方式
由于使用了@Builder注解,默认的Builder会覆盖字段上的集合初始化逻辑,可能导致多个实体实例复用同一个集合对象。可以通过@Builder.Default注解保留字段初始化:
@OneToMany(mappedBy = "refundInitiate", cascade = CascadeType.ALL, orphanRemoval = true) @Builder.Default List<AutomatedRefundLedger> refundLedgers = new ArrayList<>();
或者在无参构造方法中初始化集合,确保每个实体实例都拥有独立的集合对象:
public RefundInitiate() { this.refundLedgers = new ArrayList<>(); }
3. 自定义更新SQL绕过实体状态管理
如果暂时无法升级版本或修改实体代码,可以使用自定义@Query直接执行更新操作,避免加载实体及关联集合:
@Repository public interface AutomatedRefundLedgerRepository extends JpaRepository<AutomatedRefundLedger, Integer> { @Modifying @Query("UPDATE AutomatedRefundLedger l SET l.yourField = :value, l.updatedBy = :updatedBy, l.updatedOn = CURRENT_TIMESTAMP WHERE l.id = :id") void updateLedgerById(@Param("id") Integer id, @Param("value") Object value, @Param("updatedBy") Integer updatedBy); }
调用该方法时,需要手动传入审计字段的值(如updatedBy),或者结合@LastModifiedBy的逻辑获取当前审计用户ID。
内容的提问来源于stack exchange,提问作者prince
相关产品推荐
相关产品推荐

