Search code examples
jpajakarta-eeeclipselinkjpa-2.0

Why CascadeType.DETACH is not working in OneToMany relationships of FetchType.LAZY in Eclipselink?


In a standard JEE application with JPA, I have a master entity A that contains One to Many collection of B. The A Entity has the following form:

@Table(name = "TABLE_A")
@Entity
public class A implements Serializable {

    @Id
    @SequenceGenerator(name = "A_ID_GENERATOR", sequenceName = "SEQ_A", allocationSize = 1, initialValue = 1)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "A_ID_GENERATOR")
    @Column(unique = true, nullable = false, precision = 16)
    private Long id;

    @OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY  , mappedBy = "a")
     private List<B> bCollection;
     public List<B> getB() {
        return this.bCollection;
     }

     public void setB(List<B> bCollection) {
        this.bCollection = bCollection;
     }

     public Long getId() {
         return id;
     }

     public void setId(Long id) {
         this.id = id;
     }

}

The Entity B has the following form :

@Table(name = "TABLE_B")
@Entity
public class B implements Serializable {

    @Id
    @SequenceGenerator(name = "B_ID_GENERATOR", sequenceName = "SEQ_B", allocationSize = 1, initialValue = 1)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "B_ID_GENERATOR")
    @Column(unique = true, nullable = false, precision = 16)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "A_FK")
    private A a;

    public Long getId() {
        return this.id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public A getA() {
        return this.a;
    }

    public void setA(A a) {
        this.a = a;
    }

}

In an EJB method with @TransactionAttribute(TransactionAttributeType.REQUIRED) I retrieve an A from DB and call getB() to fetch the data of B under current A. Then I manually detach the current A with :

em.detach(a);

Before the return of the EJB method, if I test the B instances under current A with em.contains(b) they are still managed even if I use CascadeType.ALL.

The transactional method in EJB seems like the following :

@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void doSomething(String aBusinessKey) {

    A a = fetchAByItsBusinessKey(aBusinessKey);
    List<B> bs = a.getB();

    em.detach(a);

    //Test if Bs are managed
    boolean isManaged = em.contains(bs.get(0));

}

Could anyone explain why CascadeType.DETACH is blocked by FetchType.LAZY? When I change Fetch Type to EAGER the detaching is propagated to detail collection of B.

As engine I use Eclipse Link.

--EDIT The problem is that CascadeType.DETACH is not propagated in detail collections. All entities are managed and fetched before detaching.


Solution

  • Detach only cascades over fetched properties and relationships, and you are fetching the list of Bs after detaching A. getB only returns the provider's collection implementation - a proxy over the actual collection, and does not fetch the results. Only accessing this collection, such as calling size on it, will trigger the fetch.

    On other providers, this would cause an exception, but EclipseLink allows fetching lazy relationships as long as the context is still available. If the entity wasn't serialized and the EMF is still open, to keep object identity, EclipseLink will use the EntityManager the entity was read from to fetch your collection, causing the results to be managed in that EntityManager.