Search code examples
javahibernatejpaplayframeworkannotations

JPA: Find by Interface instead of Implementation


I want to use the find method with an interface, instead of using with the implementation.

That said here is my Code:

public Goods findGoods(Long goodsId) {
    return this.jpaApi.withTransaction(()->{
        Goods goods = null;
        try{
            EntityManager em = this.jpaApi.em();
            Query query = em.createQuery("select g from Goods g where id=:id", Goods.class);
            query.setParameter("id", goodsId);
            goods = (Goods) query.getSingleResult();
        }catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
        return goods;
    });
}

My Entity:

@Entity(name = "Goods")
@Table(name = "GOODS")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class GoodsImp implements Goods,  Serializable {
..
}

My Interface:

@JsonTypeInfo(include = JsonTypeInfo.As.PROPERTY, property = "type", 
use = Id.NAME, defaultImpl = GoodsImp.class, visible = true)
@JsonSubTypes({ @Type(value = ProductImp.class, name = "product"),
                @Type(value = ServiceImp.class, name = "service") })
@ImplementedBy(GoodsImp.class)
public interface Goods {
..
}

Error:

java.lang.IllegalArgumentException: Unable to locate persister: interfaces.Goods at org.hibernate.internal.SessionImpl.find(SessionImpl.java:3422) at org.hibernate.internal.SessionImpl.find(SessionImpl.java:3365) at repository.JPAGoodsRepository.lambda$deleteGoods$2(JPAGoodsRepository.java:58) at play.db.jpa.DefaultJPAApi.lambda$withTransaction$3(DefaultJPAApi.java:197)

My doubt is why i have this issue, if when I use a Query statement works fine with the interface.

This works:

 @Override
    public Collection<Goods> getAllGoods() {
        return this.jpaApi.withTransaction(() -> {
            Collection<Goods> goods = null;
            try {
                EntityManager em = this.jpaApi.em();
                Query query = em.createQuery("Select g from Goods g", Goods.class);
                goods = query.getResultList();
            } catch (Exception e) {
                // TODO: handle exception
                e.printStackTrace();
            }
            return goods;
        });

    }

Solution

  • The EntityManager method createQuery you used is declared as:

    <T> TypedQuery<T> createQuery(String qlString, Class<T> resultClass)
    

    The only reason for the Class<T> resultClass parameter is to infer the type of T, so that you could write:

    List<Goods> listOfGoods = em.createQuery("select g from Goods g where id=:id", Goods.class).getResultList();
    

    without getting compiler warnings. The resultClass parameter is certainly not telling the entityManager which entity type you are querying for. This is done by the select g from Goods g part of the query, and incidentally, Goods is an alias for the entity GoodsImpl (you could annotate the GoodsImpl entity with @Entity(name = "Bads") and select b from Bads b would also work).

    Now, if I understand correctly, you're asking why the call em.find(entityClass, primaryKey) fails when Goods.class is used for entityClass. You can find the answer in the javadoc for EntityManager, in which find is said to throw:

    IllegalArgumentException - if the first argument does not denote an entity type

    An entity type is, unsurprisingly, a class annotated with @Entity.

    If you're asking why this is the way it is implemented, then the answer is pretty simple. An interface can be implemented by several classes. Suppose you had multiple entity classes implementing Goods, each with its own table and its own id. There would be no reason for ids not overlap across these different entities. How is JPA supposed to know which of these entities you're hoping to get?