Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JPA EntityGraph, create subgraph of PluralAttribute programmably by using static MetaModel

Tags:

When I have below entity.

@Entity
class Article {
  @OneToMany
  Set<Comment> comments;
}

@Entity
class Comment {
  @ManyToOne
  User author;
}

And create some view specification class using EntityGraph and static metamodel like below.

class ArticleViewSpec {

  /**
   * fetch comments and each of comment's author.
   */
  public static final Function<EntityManager, EntityGraph<Article>> DETAIL = em -> {
    EntityGraph<Article> graph = em.createEntityGraph(Article.class);

    Subgraph<Comment> sgComment = graph.addSubgraph(Article_.comments);

    sgComment.addAttributeNodes(Comment_.author);

    return graph;
  };
}

But above class can't be compiled, because expected type is not

Subgraph<Comment> sgComment = graph.addSubgraph(Article_.comments);

but

Subgraph<Set<Comment>> sgComment = graph.addSubgraph(Article_.comments);

This problem occurs when we have attribute that extends javax.persistence.metamodel.PluralAttribute. (e.g. SetAttribute, ListAttribute)

This behaviour is obviously from api spec. javax.persistence.EntityGraph#addSubgraph(javax.persistence.metamodel.Attribute<T,X>)

But how can I create EntityGraph programmably and type-safely using JPA static MetaModel in these case ?

Workaround

/**
 * fetch comments and each of comment's author.
 */
public static final Function<EntityManager, EntityGraph<Article>> DETAIL = em -> {
    EntityGraph<Article> graph = em.createEntityGraph(Article.class);

    Subgraph<Comment> sgComment =
      graph.addSubgraph(Article_.comments.getName(), Comment.class);

    sgComment.addAttributeNodes(Comment_.author);

    return graph;
  };
like image 612
Yuki Yoshida Avatar asked Jul 31 '17 07:07

Yuki Yoshida


1 Answers

I ran into the same issue, and after doing some research I'm pretty certain this is a flaw in the JPA API.

The implementation in EclipseLink seems to do the correct thing:

public <T> Subgraph<T> addSubgraph(Attribute<X, T> attribute) {
    Class type = attribute.getJavaType();
    if (attribute.isCollection()) {
        type = ((PluralAttribute) attribute).getBindableJavaType();
    }
    return addSubgraph(attribute.getName(), type);
}

Notice how the implemetation violates the declaration from the interface when a PluralAttribute is given: With T of the Attribute being a Collection<something>, the method does not actually return a Subgraph<Collection<something>> as declared but an instance of Subgraph<something> instead.

To me it looks as though the API is actually broken in that respect, and noone seems to care because probably not many people make use of EntityGraphs and the static metamodel, though this would be a good thing to do.

Someone should create an issue somewhere to get that part of the API fixed.

My current 'fix' for the problem is a kind of custom EntityGraphBuilder which accepts SingularAttribute<E,X> and PluralAttribute<E,?,X> to allow type safe-ish creation of EntityGraphs. Basically, I just have my EntityGraphElements representing an attribute, or node in the tree, and plug them together via type-safe generic methods. This also has the benefit of merging the distinct for-whatever-reason EntityGraph and SubGraph interfaces under a common API, so that EntityGraph representations can be created and used and re-used as subgraphs in other entity graphs.

My solution could probably be applied to the JPA API too, it basically just splits up the

<T> Subgraph<T> addSubgraph(Attribute<X, T> attribute)

into two methods:

<F> EntityGraphElement<F> fetch(SingularAttribute<? super T, F> attr);

and

<F> EntityGraphElement<F> fetch(PluralAttribute<? super T, ?, F> attr);

Update:

Using some generics Voodoo, one method is enough:

<F, A extends Attribute<? super T, ?> & Bindable<F>> EntityGraphElement<F> fetch(A attribute);
like image 173
JimmyB Avatar answered Oct 11 '22 16:10

JimmyB