Criteria API: filter by class type

2019-02-17 22:28发布

问题:

I'm relativley new to relational databases and I have some problems concerning the creation of queries. First I want to explain the situation shortly. I have several entity classes. All of them extend AbstractEntity or EntityProperty. So entities can have properties and properties have owning entities, so there is a bidirectional relation.
Now let's say ConcreteEntity extends AbstractEntity and I want to create queries like this: Get all entities of type ConcreteEntity which has at least on property with a name contained in the given list propertyNames. Until now I have the following working criteria query:

CriteriaQuery<AbstractEntity> cq = cb.createQuery(AbstractEntity.class);
Root<EntityProperty> property = cq.from(EntityProperty.class);
Join<EntityProperty, AbstractEntity> entity = property.join(EntityProperty_.owningEntities);
cq.where(property.get(EntityProperty_.name).in((Object[]) propertyNames));
cq.select(entity);

But now I want only those entities of type ConcreteEntity. How could I achieve this? In JPQL I wrote "SELECT entity FROM EntityProperty property JOIN property.owningEntities entity" and here I also have no idea how to write it in the way that only a specific type is returned...

Thanks for answers in advance!

EDIT: moved the second question to criteria query: indistinct result lists and removed distinct in the code (that didn't work)

回答1:

I know this is an old question but just in case someone stumbles upon the same problem, here is how it can be solved. You can easily filter by entity type like this:

Predicate p = cb.equal(entity.type(), cb.literal(ConcreteEntity.class));

where entity can be a Path (Root and Join included), cb is a CriteriaBuilder object. So in your case it would be something like this:

CriteriaQuery<AbstractEntity> cq = cb.createQuery(AbstractEntity.class);
Root<EntityProperty> property = cq.from(EntityProperty.class);
Join<EntityProperty, AbstractEntity> entity = property.join(EntityProperty_.owningEntities);
cq.where(cb.and(
    property.get(EntityProperty_.name).in((Object[]) propertyNames),
    cb.equal(entity.type(), cb.literal.ConcreteEntity.class)
));
cq.select(entity);


回答2:

The only way I found until now was to create an enumeration with a value for each class The resulting criteria query is

CriteriaQuery<AbstractEntity> cq = cb.createQuery(AbstractEntity.class);
Root<EntityProperty> property = cq.from(EntityProperty.class);
SetJoin<EntityProperty, AbstractEntity> entity =
                property.join(EntityProperty_.owningEntities);
cq.where(property.get(EntityProperty_.name).in((Object[]) propertyNames),
                entity.get(AbstractEntity_.entityType).in(suitableSubTypes));
cq.select(entity);
List<AbstractEntity> resultList = em.createQuery(cq).getResultList();

As you can see, every entity now has the attribute entityType. I also have to create the collection suitableSubTypes every time. Another problem is that the returned type is List<AbstractEntity>. What I wanted was a method signature like

public static <T extends AbstractEntity> List<T>
                getEntities(Class<T> entityClass, String... propertyNames)

but for now I have

public static List<AbstractEntity>
                getEntities(Collection<AbstractEntityType> suitableSubTypes,
                String... propertyNames)

So I still hope there exists a better solution...