Jovanny Cruz
Jovanny Cruz

Reputation: 215

Spring Data JPA - Delete many to many entries

I am attempting to remove entries from a many to many relationship using Spring Data JPA. One of the models is the owner of the relationship and I need to remove entries of the non-owner entity. These are the models:

Workflow entity

@Entity(name = "workflows")
public class Workflow {
    @Id
    @Column(name = "workflow_id", updatable = false, nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private UUID workflowId;

    @ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
    @JoinTable(name = "workflow_data",
            joinColumns = @JoinColumn(name = "workflow_id", referencedColumnName = "workflow_id"),
            inverseJoinColumns = @JoinColumn(name = "data_upload_id", referencedColumnName = "data_upload_id"))
    private Set<DataUpload> dataUploads = new HashSet<>();

    // Setters and getters...
}

DataUpload entity

@Entity(name = "data_uploads")
public class DataUpload {
    @Id
    @Column(name = "data_upload_id")
    private UUID dataUploadId;

    @ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE }, mappedBy = "dataUploads")
    private Set<Workflow> workflows = new HashSet<>();

    // Setters and getters...
}

DataUpload repository

@Repository
public interface DataUploadsRepository extends JpaRepository<DataUpload, UUID> {
    @Transactional
    void delete(DataUpload dataUpload);
    Optional<DataUpload> findByDataUploadId(UUID dataUploadId);
}

To delete data uploads, I am trying to execute a couple of query methods of the repository:

First version

dataUploadsRepository.deleteAll(workflow.getDataUploads());

Second version

workflow.getDataUploads().stream()
            .map(DataUpload::getDataUploadId)
            .map(dataUploadsRepository::findByDataUploadId)
            .filter(Optional::isPresent)
            .map(Optional::get)
            .forEach(dataUploadsRepository::delete);

Problem is that Spring Data JPA is not removing DataUploads nor entries of the association table workflow_data.

How can I tell Spring Data to remove from both data_uploads and workflow_data (association table)?

I would appreciate any help.

Upvotes: 3

Views: 10104

Answers (2)

Jovanny Cruz
Jovanny Cruz

Reputation: 215

I found the solution for this problem. Basically, both entities (in my case) need to be the owner of the relationship and the data from the association table must be deleted first.

Workflow entity (relationship owner)

@Entity(name = "workflows")
public class Workflow {
    @Id
    @Column(name = "workflow_id", updatable = false, nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private UUID workflowId;

    @ManyToMany(cascade = { CascadeType.ALL })
    @JoinTable(name = "workflow_data",
            joinColumns = @JoinColumn(name = "workflow_id", referencedColumnName = "workflow_id"),
            inverseJoinColumns = @JoinColumn(name = "data_upload_id", referencedColumnName = "data_upload_id"))
    private Set<DataUpload> dataUploads = new HashSet<>();

    // Setters and getters...
}

DataUpload entity (relationship owner)

@Entity(name = "data_uploads")
public class DataUpload {
    @Id
    @Column(name = "data_upload_id")
    private UUID dataUploadId;

    @ManyToMany
    @JoinTable(name = "workflow_data",
            joinColumns = @JoinColumn(name = "data_upload_id", referencedColumnName = "data_upload_id"),
            inverseJoinColumns = @JoinColumn(name = "workflow_id", referencedColumnName = "workflow_id"))
    private Set<Workflow> workflows = new HashSet<>();

    // Setters and getters...
}

Notice that Workflow has ALL as cascade type, since (based on the logic I need), I want Spring Data JPA to remove, merge, refresh, persist and detach DataUploads when modifying workflows. On the other hand, DataUpload does not have cascade type, as I do not want Workflow instances (and records) to be affected due to DataUploads deletions.

In order to successfully delete DataUploads, the associate data should be deleted first:

public void deleteDataUploads(Workflow workflow) {
    for (Iterator<DataUpload> dataUploadIterator = workflow.getDataUploads().iterator(); dataUploadIterator.hasNext();) {
        DataUpload dataUploadEntry = dataUploadIterator.next();
        dataUploadIterator.remove();

        dataUploadsRepository.delete(dataUploadEntry);
    }
}

dataUploadIterator.remove() deletes records from the association table (workflow_data) and then the DataUpload is deleted with dataUploadRepository.delete(dataUploadEntry);.

Upvotes: 3

psabbate
psabbate

Reputation: 777

It has been a while since I have to fix these kind of mappings so I'm not going to give you a code fix, instead maybe give you another perspective.

First some questions like, do you really need a many to many? does it make sense that any of those entities exist without the other one? Can a DataUpload exist by itself?

In these mappings you are supposed to unassign the relationships on both sides, and keep in mind that you could always execute a query to remove the actual values (a query against the entity and the intermediate as well)

A couple of links that I hope can be useful to you, they explain the mappings best practices and different ways to do the deletion.

Delete Many, Delete Many to Many, Best way to use many to many.

Upvotes: 0

Related Questions