0

I have the following issues :

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

And:

Caused by: java.util.ConcurrentModificationException: null

I see where is the problem.

I try to update a child entity, relationship ManyToOne with the parent. When I update the child, the refresh wasn't good. If I change the parent, it keeps the child. It isn't good. So, I'm trying to get the "old" parent, remove the child from it's list and then continue the update. But when I remove the child, it trigger's the @Preremove annotation. And I don't want that.

Here's the child:

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

There's the service (@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);
        }
....
}

Do you have any idea?

EDIT 1:

removeJob:

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

EDIT 2:

Project:

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

1 Answers1

1

As explained in JPA - @PreRemove method behaviour, @PreRemove is triggered by the removal of the orphan job.

You're properly synchronizing both ends of the Projet-Job bi-directional association, and perhaps you should avoid using @PreRemove to perform other bi-directional association synchronization, but rather do it in add* and remove* methods, and not elsewhere.

You don't need to call repository.save so often. In a transactional method, changes to managed entities are automatically propagated, so you should only use it on unmanaged entities (here, only 1 time for job which is created by MapStruct). If you really need to flush, you can use the repository.saveAndFlush method, but it's not necessary here at first sight.

By the way, you kept the JHipster generated code that creates and updates entities with the same save method, which merges everything from the DTO. I recommend having separate create and update methods to avoid unwanted updates amongst other issues. You can also leverage MapStruct for updates: https://mapstruct.org/documentation/stable/reference/html/#updating-bean-instances

Hope this helps!