首页 > 解决方案 > 原因:java.util.ConcurrentModificationException: null with Preremove annotation

问题描述

我有以下问题:

Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction

和:

Caused by: java.util.ConcurrentModificationException: null

我知道问题出在哪里。

我尝试更新子实体,与父实体的关系 ManyToOne。当我更新孩子时,刷新并不好。如果我改变父母,它会保留孩子。这不好。因此,我正在尝试获取“旧”父级,将子级从其列表中删除,然后继续更新。但是当我删除孩子时,它会触发 @Preremove 注释。我不希望那样。

这是孩子:

@Entity
@Table(name = "job", schema = "public")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Job implements Serializable {
...
...
    @ManyToOne(cascade = CascadeType.REFRESH,fetch = FetchType.LAZY)
    @OnDelete(action = OnDeleteAction.CASCADE)
    @JsonIgnoreProperties(value = "jobs", allowSetters = true)
    private Project project;
..
...
...
    public Project getProject() {
        return project;
    }

    public Job project(Project project) {
        this.project = project;
        return this;
    }

    public void setProject(Project project) {
        this.project = project;

    }
...
...
...
    @PreRemove
    public void removeAppUsers() {

        if(this.appUsers.size()>0)
        for (AppUser ap : this.appUsers) {
            ap.removeJob(this);
        }
        if(this.performances.size()>0)
        for (Performance p : this.performances) {
            p.setJob(null);
        }
    }

有服务(@Service,@Transactional):

public JobDTO save(JobDTO jobDTO) {
        Optional<Project> projectToRemove;
        Project p;
        log.debug("Request to save Job : {}", jobDTO); 
        Job job = jobMapper.toEntity(jobDTO);
        projectToRemove=projectRepository.findProjectByJob(jobDTO.getId());
        jobRepository.save(job);
        if(projectToRemove.isPresent()&&jobDTO.getProjectId()!=projectToRemove.get().getId()) {
                projectToRemove.get().removeJob(job);
                projectRepository.save(projectToRemove.get());
        }     
        if (jobDTO.getProjectId() != null) {
            
            p = projectRepository.findById(jobDTO.getProjectId()).get();
            p.addJob(job);
            projectRepository.save(p);
            job.setProject(p);
            jobRepository.save(job);
        }
....
}

你有什么主意吗?

编辑1:

删除作业:

public Project removeJob(Job job) {
    this.jobs.remove(job);
    job.setProject(null);
    return this;
}

编辑2:

项目:

@OneToMany(mappedBy = "project", cascade = CascadeType.ALL, orphanRemoval = true)
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private Set<Job> jobs = new HashSet<>();

标签: javaspring-boothibernatejpa

解决方案


正如JPA - @PreRemove method behavior中所解释的,@PreRemove 由孤立作业的删除触发。

您正在正确同步 Projet-Job 双向关联的两端,也许您应该避免使用@PreRemove来执行其他双向关联同步,而是在add*remove*方法中进行,而不是在其他地方进行。

你不需要repository.save这么频繁地打电话。在事务方法中,对托管实体的更改会自动传播,因此您应该只在非托管实体上使用它(此处,jobMapStruct 仅为其创建 1 次)。如果真的需要刷机,可以使用repository.saveAndFlush方法,但是这里乍一看没必要。

顺便说一句,您保留了 JHipster 生成的代码,该代码使用相同的save方法创建和更新实体,该方法合并了 DTO 中的所有内容。我建议使用单独的创建和更新方法以避免不必要的更新以及其他问题。您还可以利用 MapStruct 进行更新: https ://mapstruct.org/documentation/stable/reference/html/#updating-bean-instances

希望这可以帮助!


推荐阅读