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:
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?
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/