Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Criteria API: filter by class type

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)

like image 906
Jogi Avatar asked Sep 29 '22 17:09

Jogi


2 Answers

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);
like image 69
Armando Avatar answered Oct 03 '22 03:10

Armando


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...

like image 22
Jogi Avatar answered Oct 03 '22 03:10

Jogi