JPA PersistentObjectException:传递给persist的分离实体

时间:2015-09-28 17:39:30

标签: java spring hibernate jpa

我在尝试使用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的目的背道而驰。

更新

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

@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);
}

2 个答案:

答案 0 :(得分:1)

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

容器不拦截类内方法调用(并且通过定义私有方法只能以这种方式调用),因此对于这些注释@Transactional没有任何影响。所以我认为你需要检查你的交易边界实际上在哪里。

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

首先,你必须确保方法saveAppointments本身实际上在一个事务中运行(例如,使它成为public并直接从你@Inject之前调用它),然后你必须确保后续方法调用重用同一个事务,而不是启动一个新事务(即没有@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)

答案 1 :(得分:0)

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

所以你把你的数据库连接到你的持久层,现在你想知道,为什么你的生命一旦离开他们的生态系统就会中断。

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

如何完成此操作,您可以阅读What is the proper way to re-attach detached objects in Hibernate?

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