JPA Criteria API: LEFT JOIN for optional relationships

Kawu picture Kawu · Sep 3, 2013 · Viewed 29.7k times · Source

I'm using the Criteria API basically the first time. It's about abstracting queries for a generic builder:

public TypedQuery<T> newQuery( Manager<?,T> manager )
{
    CriteriaBuilder builder = this.entityManager.getCriteriaBuilder();

    Class<T> genericClass = ( Class<T> ) ( ( ParameterizedType ) manager.getClass().getGenericSuperclass() ).getActualTypeArguments()[1];

    CriteriaQuery<T> criteriaQuery = builder.createQuery( genericClass );
    Root<T> root = criteriaQuery.from( genericClass );

    ...
}

The call criteriaQuery.from( genericClass ); generates SQL INNER JOIN for all relationships found on the entity by default. This is a problem, because every relationship being null (DB NULL or a DB that doesn't use foreign keys and has an invalid reference) those entities will be missing in the result list, effectively producing wrong search results.

An example can be found here: JPA Criteria query Path.get left join is it possibile

What I'd like to happen for queries instantiated by this class/method is that all relationships on the entity, here genericClass, that are mapped as optional = true

@ManyToOne( FetchType.EAGER, optional = true )
@JoinColumn( name = "CLOSE_USER_ID", referencedColumnName = "USER_ID" )
private User              closer;

to generate an SQL LEFT (OUTER) JOIN instead of INNER JOIN.

Question:

Is there standard JPQ way to get this done? If so, how?

PS: there's generally no way to know the concrete type beforehand, so the only way I might be able to achieve what I need is to use the metamodel of some sort and generate the joins manually (which I'd like to avoid).


We are using EclipseLink 2.3

Answer

James picture James · Sep 3, 2013

.from(class) does not use an INNER join for all relationships, it only queries the class.

A relationship will only be queried if you use the join() or fetch() API, to use an outer join use join() with a JoinType.LEFT.

https://en.wikibooks.org/wiki/Java_Persistence/Criteria#Join

I'm not sure why you are seeing joins if you are not calling join(). Some JPA providers automatically join fetch all EAGER relationships, this may be what you are seeing. I have always though this odd, perhaps your JPA provider has a away to be configured not to do this, or you can make the relationships LAZY.