javaspring-data-jparelationshipjpa-2.0cascading

Understanding Relationship Cascading with Merge


Deep diving into Spring JPA and I have troubles to understand whats going on here. This is the code:

Entity

@Entity
public class PersonP {

    @Id @GeneratedValue
    private int id;

    public String name;

    @ManyToMany
    public List<PersonP> knows = new LinkedList<>();

    @ManyToMany(mappedBy = "knows", cascade = CascadeType.ALL)
    public List<PersonP> knownBy = new LinkedList<>();

    public PersonP(){}

    public PersonP(String name) {
        this.name = name;
    }
}

SpringBootApp

@SpringBootApplication
@EntityScan(basePackageClasses = Demo.class)
public class Demo implements CommandLineRunner {

    @PersistenceContext
    EntityManager em;

    public static void main(String[] args) {
        new SpringApplicationBuilder(Demo.class).run(args);
    }

    @Override
    @Transactional
    public void run(String... args) throws Exception {
        sample();
    }

    @Transactional
    public void sample(){
        PersonP p1 = new PersonP("P1");
        PersonP p2 = new PersonP("P2");
        PersonP p3 = new PersonP("P3");
        PersonP p4 = new PersonP("P4");
        PersonP p5 = new PersonP("P5");

        p1.knows.add(p2);
        p2.knows.add(p3);
        p5.knows.add(p3);

        p1.knownBy.add(p3);
        p3.knownBy.add(p2);
        p2.knownBy.add(p4);

        p1 = em.merge(p1);

        p1.name = "x1";
        p2.name = "x2";
        p3.name = "x3";
        p4.name = "x4";
        p5.name = "x5";
    }

By starting the app, the following gets generated in the DB: enter image description here

I get to the point where all the persons are generated, but I would expect one additional entry in the PERSONP_KNOWS table, e.g. for p2.knownBy.add(p4); But it isnt. I assumed since calling merge the associated entities would be saved too (p1 knows p2 and p2 is known by p4). It doesn't seem to be the case though... Why is this so?


Solution

  • As you notice, not all persons are persisted (P5 is missing in the DB), and neither are all the relationships you probably intended.

    The reason that P5 is missing, is that you cascade the merge operation only along the knownBy-associated persons:
    P1 -> P3 -> P2 -> P4
    (P5 is not knownBy anyone, so it is not reached by the cascaded merge)


    When each of the four entities gets persisted, so is the information on the associations for which it holds the owning side.
    In your case, the owning side is knows (as knownBy is mappedBy="knows").
    So the only association-information which is persisted is:
    P1 (ID1) -> P2 (ID3)
    P2 (ID3) -> P3 (ID2)

    (note that P2 has ID 3, which is confusing, but makes sence since the ID is generated and it was later persisted)

    P5 -> P3 is not persisted since P5 is not persisted at all (as explained above)


    So to properly persist all information, always make sure that bi-directional associations are synchronized. And be aware of potential problems when cascading operations on @ManyToMany associations, since this can lead to performance problems (lots of cascaded operations) and unintended results (e.g. when cascading removes).

    https://vladmihalcea.com/jpa-hibernate-synchronize-bidirectional-entity-associations/
    https://thorben-janssen.com/avoid-cascadetype-delete-many-assocations/