【问题标题】:JPA PersistentObjectException: detached entity passed to persistJPA PersistentObjectException:分离的实体传递给持久化
【发布时间】:2015-12-26 00:52:56
【问题描述】:

我在尝试使用 Spring Data JPA 和 Hibernate 作为 JPA 提供程序进行批量插入操作时遇到问题。

我的服务中有以下方法。这是抛出异常的地方。

    @Transactional
    private void saveAppointments() {

        // create StageFile reference object
        StageFile file = new StageFile();
        file.setFileName("3312_APPOINTMENT.API");
        file.setDeleteFlag('N');
        file.setInstitution(institution);

        for (StageAppointment appointment : appointments) {
            appointment.setStageFile(file);
            stageAppointmentRepository.save(appointment);
        }
    }

    @Transactional
    private void saveDepartments() {

        // create StageFile reference object
        StageFile file = new StageFile();
        file.setFileName("3312_DEPARTMENT.API");
        file.setDeleteFlag('N');
        file.setInstitution(institution);

        for (StageDepartment department : departments) {
            department.setStageFile(file);
            stageDepartmentRepository.save(department);
        }
    }

机构是一个实例变量,提前获取。

Institution institution = institutionRepository.findByActCode(3312);

我还将实体设置为级联 PERSIST 和 MERGE。

@Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "stgAppointmentSeq")
    @SequenceGenerator(name = "stgAppointmentSeq", sequenceName = "T_STG_APPOINTMENT_SEQ", allocationSize = 50)
    @Column(name = "ID")
    private Long id;

    @ManyToOne(cascade = {CascadeType.PERSIST,CascadeType.MERGE}, fetch = FetchType.EAGER, optional = false)
    @JoinColumn(name = "FILE_ID", referencedColumnName = "ID")
    private StageFile stageFile;

    @ManyToOne(cascade = {CascadeType.PERSIST,CascadeType.MERGE}, fetch = FetchType.EAGER, optional = false)
    @JoinColumn(name = "STATUS_ID", referencedColumnName = "ID")
    private StageStatus stageStatus;

我做错了什么?

我也确定这个问题的答案是肯定的,但是当我持久化具有所需外键引用的实体时,我必须保存完整的关联对象还是只保存 ID?不过似乎与 JPA 的目的背道而驰。

更新:

根据 cmets,我更新了代码以在单个事务中执行所有操作,但没有任何区别。

@Transactional
private void saveAppointments() {

    Institution institution = institutionRepository.findByActCode(3312);
    StageStatus stageStatus = stageStatusRepository.findOne(1L);

    // create StageFile reference object
    StageFile file = new StageFile();
    file.setFileName("3312_APPOINTMENT.API");
    file.setDeleteFlag('N');
    file.setInstitution(institution);

    for (StageAppointment appointment : appointments) {
        appointment.setStageFile(file);
        appointment.setStageStatus(stageStatus);
        stageAppointmentRepository.save(appointment);
    }
}

更新 2:

为什么这段代码有效

    @Transactional
    private void saveUsingTransaction() {

        Institution institution = institutionRepository.findByActCode(3312);
        StageStatus status = stageStatusRepository.findOne(1L);

        StageFile file =  new StageFile();
        file.setDeleteFlag('N');
        file.setFileName("3312_DIRECTORY.API");
        file.setInstitution(institution);

        StageDirectory directory = new StageDirectory();
        directory.setLocalId("11111111111111111");
        directory.setFirstName("Joe");
        directory.setLastName("Joe");
        directory.setPrimaryEmail("joe@gmail.com");
        directory.setStageFile(file);
        directory.setStageStatus(status);

        stageDirectoryRepository.save(directory);
    }

而这段代码没有

@Transactional
private void savePassingDirectory(StageDirectory directory) {

    Institution institution = institutionRepository.findByActCode(3312);
    StageStatus stageStatus = stageStatusRepository.findOne(1L);

    // create StageFile reference object
    StageFile file = new StageFile();
    file.setFileName("3312_DIRECTORY.API");
    file.setInstitution(institution);
    file.setDeleteFlag('N');

    directory.setStageFile(file);
    directory.setStageStatus(stageStatus);
    stageDirectoryRepository.save(directory);
}

【问题讨论】:

  • 查看其他答案Unidirectional one to many association in Hibernate / spring-data-jpa。问题是您的交易在哪里开始和停止。您检索的实体需要在同一个事务中。 institution 被加载到不同的事务中,这就是您收到错误的原因。请阅读另一个问题中的链接,这样您就可以避免几周的痛苦和沮丧。
  • 更新了要在单个事务中处理的代码,但仍然出现相同的错误

标签: java spring hibernate jpa


【解决方案1】:

@Transactional 仅适用于从外部对象到托管 bean 的方法调用。

类内方法调用(并且根据定义只能以这种方式调用私有方法)不会被容器拦截,因此对于这些注释@Transactional 无效。所以我认为你需要检查你的交易边界实际在哪里。

我的猜测是saveAppointments 当前在任何事务之外运行。您的 institutionRepository.findByActCode() 可能是对托管 bean 的正确调用,以便使用事务。当该方法再次返回时,您在调用 stageAppointmentRepository.save() 时再次执行任何事务之外的操作以最终进入新事务。

首先您必须确保方法saveAppointments 本身确实在事务中运行(例如,将其设为public 并直接从您@Inject 的任何位置调用它),然后您必须确保后续方法调用重用同一个事务,而不是开始一个新事务(即没有@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)

【讨论】:

  • 你是对的,我什至没有注意到该方法是私有的,这解释了为什么交易被破坏了!
【解决方案2】:

有趣的是,每次有人使用实体管理器一段时间后,就会弹出这个问题......

所以你将你的数据库连接到你的持久层,现在你想知道,如果你的实体离开他们的生态系统,为什么他们会崩溃。

原因很简单,因为一旦实体离开其边界,即事务上下文,它就会与实体管理器分离。 如果要存储其更改,则必须再次附加它。

这是如何完成的,您可以在What is the proper way to re-attach detached objects in Hibernate?阅读

在您的情况下,appointments 已分离。所以你必须检查你的.save(...)方法,如果传递的对象在当前实体管理器的上下文中,如果不是,重新附加它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-11-02
    • 2013-06-26
    • 2019-12-10
    • 2017-10-21
    相关资源
    最近更新 更多