You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 19:04:52