Kimi
Kimi

Reputation: 6289

How to create an 'instance of' -like query in JPA 2.0?

Say we've got an abstract @Entity Animal, and several entity classes that extend Animal, including Dog, Cat, Monkey and Bat.

How can I filter the results based on the extending entity's class?

Example: There are checkboxes where the user can select which entities to retrieve.

[ ] Dog
[X] Cat
[X] Monkey
[ ] Bat

Now I want to retrieve the entities with a (Named)Query defined in the Animal class. What kind of query parameters can I put into the query so that only the Cat and Monkey objects will be returned?

Upvotes: 13

Views: 13262

Answers (3)

Archie
Archie

Reputation: 5421

Here's what I use:

    /**
     * Build an "instanceof" {@link Predicate} for an entity type.
     */
    @SuppressWarnings("unchecked")
    public static Predicate instanceOf(EntityManager entityManager, Path<?> path, Class<?> type) {
        Preconditions.checkArgument(entityManager != null, "null entityManager");
        Preconditions.checkArgument(path != null, "null path");
        Preconditions.checkArgument(type != null, "null type");
        final Set<Class<?>> subtypes = entityManager.getMetamodel().getManagedTypes().stream()
          .map(ManagedType::getJavaType)
          .filter(type::isAssignableFrom)
          .collect(Collectors.toSet());
        return JPAUtil.in(entityManager.getCriteriaBuilder(), (Expression<Class<?>>)path.type(), subtypes);
    }

    /**
     * Build an IN predicate from a collection of possible values.
     */
    public static <T> Predicate in(CriteriaBuilder builder, Expression<T> expr, Iterable<? extends T> values) {
        Preconditions.checkArgument(builder != null, "null builder");
        Preconditions.checkArgument(expr != null, "null expr");
        Preconditions.checkArgument(values != null, "null values");
        final CriteriaBuilder.In<T> in = builder.in(expr);
        values.forEach(in::value);
        return in;
    }

Upvotes: 0

JB Nizet
JB Nizet

Reputation: 691973

I'm not absolutely sure it's supported by JPA, but the way to do it in Hibernate, regardless of the inheritance strategy, and thus even if you don't have a discriminator (or didn't map it as a property) is to use the implicit class property :

String jpql = "select a from Animal a where a.class in (:classes)";
Query q = em.createQuery(jpql).setParameter("classes", 
                                            Arrays.asList(Cat.class, Monkey.class));

EDIT :

I just found it's possible in JPA2 using the TYPE operator :

String jpql = "SELECT a FROM Animal a WHERE TYPE(a) IN :classes";
Query q = em.createQuery(jpql).setParameter("classes", 
                                            Arrays.asList(Cat.class, Monkey.class));

Upvotes: 33

Shivan Dragon
Shivan Dragon

Reputation: 15229

You can use the discrimnator column and value to only search for certain subtypes of a given type. By default the discriminator column's name is DTYPE in JPA,the type is String and the value is the name of the class. You can however override this by adding the class level annotation @DiscriminatorColumn(name="KIND", discriminatorType=DiscriminatorType.INTEGER) (for the discriminator column's name and type) and @DiscriminatorValue("1") (for the specific discrimiminator value for a certain class). You can then use this in the WHERE clause of yoru JPQL query to only fetch certain subtypes, like: WHERE DTYPE="Dog" OR DTYPE="Cat"

Upvotes: 4

Related Questions