jpaeclipselinkone-to-many

Jakarta EclipseLink - how to keep bidirectional one to many relationship in sync using EntityManager


I have two classes:

@Entity
@Getter
@Setter
public class A {
    @Id
    private String aid;
    
    @OneToMany(mappedBy = "mya")
    private Collection<B> bees;    
    
    public void addB(B b2add){
        bees = bees != null ? bees : new HashSet<B>();
        bees.add(b2add);
    }
}

@Entity
@Getter
@Setter
public class B {
    @Id
    private String bid;
    
    @ManyToOne
    private A mya;
}

I try to persist two instances of type B and one of type A and keep them linked together. However, depending on the side of the persist operation I get different results, none of which are satisfying.

public Response add(){
        B b1 = new B();
        B b2 = new B();
        b1.setBid("b1");
        b2.setBid("b2");
        
        A a = new A();
        a.setAid("a1");
        a.addB(b1);
        a.addB(b2);
        
        em.persist(b1);
        em.persist(b2);
        em.persist(a);
        
         return Response
                .ok("added")
                .build();
    }

public Response add2(){
        B b1 = new B();
        B b2 = new B();
        b1.setBid("b1");
        b2.setBid("b2");
        
        A a = new A();
        a.setAid("a1");
        b1.setMya(a);
        b2.setMya(a);
        
        em.persist(b1);
        em.persist(b2);
        em.persist(a);
        
         return Response
                .ok("added inversed")
                .build();
    }

public Response read(){
        var aent = em.find(A.class, "a1");
        System.out.println(bent);

        return Response
            .ok(aent)
            .build();
        }
    }

public Response read2(){
        var bent = em.find(B.class, "b1");
        System.out.println(bent);

        return Response
            .ok(bent)
            .build();
    }

If I invoke add I get a1 associated with b1 and b2 but both instances of B have no reference to a1. On the other hand, if I invoke methodadd2 I get b1 and b2 associated with a1 but a1 has its list empty.

I looked into the data stored in a database and everything seems exactly the same for both cases (foreign keys are not null).

How can I make this relation bidirectional for traversal made via EntityManager without manual invoking first add1 then add2?


Solution

  • You need to look into the specification, but it states you are responsible for keeping your object model in sync with the database. For a bidirectional relationship, that means setting both sides.

    In this case, the A's mya reference 'owns' the relationship, and it must be set for the foreign keys to be set. This means that your test method "add" isn't doing what you think it is - you have As and Bs inserted, but not linked in your database. The A instance shows Bs because it is cached and being read back from it. If you refresh the A's and B's (or just restart your app and read them from the database), they will not have any relationships set.

    In the case of the add2 test method, B's relationsship is set to the DB because it owns the FK. A's are NOT modified in your test (they are POJOs mostly). Because of caching, while the FK relationship exists in the DB, you are only going to see what you gave to JPA to persist. This is why the spec required you to keep your own model in synch.

    The best, cheapest solution over all is just to set both sides of the relationship to ensure that what you have cached always reflects what is in the DB. That said, you can force a database hit by calling entityManager.refresh(a), use query hints ( see queryhints:refresh), or change session caching settings as desired. The later option is well documented and inadvisable unless you know the consequences to your app