Search code examples
javajpajaxbeclipselinkmoxy

How to avoid loading lazy bidirectional relationships with MOXy?


My question is a follow up to this comment.

I'm mixing JPA and JAXB (MOXy) annotations on the same class, which works fine most of the time. As described in the linked thread, @XmlInverseReference prevents cycle exceptions when bidirectional relationships are marshalled. But in order to detect the cycle, MOXy has to inspect the back reference of the linked entity, which leads to extra SQL SELECTs if a lazy relation needs to be populated.

To illustrate the problem in detail, consider this made-up example:

@Entity
@Access( AccessType.FIELD )
@XmlRootElement
@XmlAccessorType( XmlAccessType.FIELD )
public class Phone {
    @ManyToOne
    @JoinColumn( name = "employeeID" )
    @XmlElement( name = "employee" )
    @XmlInverseReference( mappedBy = "phones" )
    private Employee employee;

    private String number;

    [...]
}


@Entity
@Access( AccessType.FIELD )
@XmlRootElement
@XmlAccessorType( XmlAccessType.FIELD )
public class Employee {
    @OneToMany( mappedBy = "employee" )
    @XmlElementWrapper( name = "phones" )
    @XmlElement( name = "phone" )
    @XmlInverseReference( mappedBy = "employee" )
    private List<Phone> phones;

    private String name;

    [...]
}

Now I'd run queries on Phones with a JAX-RS method like this (using an underlying EJB):

@Inject
private PhoneService phoneService;

@GET
@Path( "/phones" )
public List<Phone> getPhonesByNumber( @QueryParam( "number" ) String number ) {
    List<Phone> result = phoneService.getPhonesByNumber( number );

    return result;
}

What happens is this: The JPQL query within the PhoneService EJB triggers an SQL SELECT on the Phone table (filtered by the number), and if I use a JOIN FETCH query, I can get the associated Employee with the same single SELECT statement.

When the JAX-RS method returns, the JAXB marshalling kicks in, which leads to an additional SQL SELECT: this one selects all Phones whose employeeID points to the Employee who is associated with the originally requested Phones. So the lazy relationship from Employee to Phone is resolved now, presumably because MOXy must be able to determine if the original Phone is contained in the collection.

I've tried using JPA property access and JAXB field access for the phones field, as suggested in the other thread, to no avail. I've also tried nulling out the phones field in the linked Employee instance after retrieving the result from the EJB, i.e. when my entities are detached already, but this led to an immediate SQL SELECT again (it seems like EclipseLink will do this whenever any manipulation is done to an IndirectList?). The only workaround solution I could find is to use MOXy @XmlNamedObjectGraphs with a subgraph that excludes the phones field. But that's not practical, especially if the involved entities have many attributes.

As I may need to query in the other direction too, e.g. employees by name with their associated phones, I can't just mark phones as @XmlTransient.

Does anyone have an elegant solution to suppress those extra SQL statements?


Solution

  • I collected some information about EclipseLink from these three threads. Important bits:

    Detached Objects get the connection need to traverse the LAZY relationship from the EntityManagerFactory and will able able to use it as long as the EntityManagerFactory is open. The connection used in not the transactional one and when you want to use the entity in a transaction it will have to be properly merged.

     

    This is a special feature of TopLink's implementation where the detached instances created from non-tx reads still have access in their proxies to retrieve additional dettached instances. If the object was detached through serialization this would not be possible.

     

    If you would like TopLink Essentials to not process lazy relationships after the EM has closed I would recommend filing an enhancement request in GlassFish.

    I couldn't find such an enhancement request though, let alone an implemented possibility to disable this feature (on a case-by-case basis).

    There are five possible workarounds I could think of, each with its own drawbacks:

    1. Just don't mix JAXB and JPA annotations on the same class: use a different set of additionatlly instantiated JAXB classes instead and perform explicit mapping between the two views. This could be a little expensive if lots of entities are returned from a query.

    2. Like I mentioned in my question, use MOXy's (named) object graph feature to exclude (relationship) fields from being traversed.

    3. Use a JAXB Marshaller.Listener to exclude all uninstantiated IndirectContainers.

    4. Since serialization is supposed to break this EclipseLink feature for detached entities, serialize them before marshalling them. Seems awkward and even more expensive though.

    5. This comes closest to emulating turning off the feature, but also looks hackish: access the wrapping IndirectContainer and its contained ValueHolderInterface and set them to null. Sample code:

    (...)

    import org.eclipse.persistence.indirection.IndirectContainer;
    
    // entities must already be detached here, otherwise SQL UPDATEs will be triggered!
    Employee e = phone.getEmployee();
    IndirectContainer container = (IndirectContainer) e.getPhones();
    container.setValueHolder( null );
    e.setPhones( null );