I'm kinda new to querydsl and currently working on a task where we want to access a OneToMany related table without using the generated Q-classes and perform a subquery on that. We're using QueryDsl 3.7.4. In this example case we've got a simple max-Statement on a date-column.
The statement I'm trying to achieve should look something like this:
select person
from person p
left join p.instructions as i
where i.validUntil = (
select max(i.validUntil) from instruction i2 where i2.person = p
)
person entity:
@OneToMany(cascade = {..},mappedBy = "person")
private Set<Instruction> instructions = new HashSet<>();
instruction entity:
@Entity(name = "instruction")
public class Instruction {
...
@Column(name = "validuntil", nullable = true)
@Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDate")
private LocalDate validUntil;
...
}
variables:
query: select baseEntity from person baseEntity [..]
pathBuilder: baseEntity
entityPath: baseEntity.instructions
entityClazz: java.util.Set
propName: validUntil
So far, I tried different solutions:
if (entityClazz.isAssignableFrom(Set.class)) {
CollectionPath<Object, PathBuilder<Object>> collection =
pathBuilder.getCollection(entityName, entityClazz);
query.leftJoin(collection.any());
if ("date".equals(columnModel.getType())) {
DateSubQuery<LocalDate> sub = new JPASubQuery().from(entityPath).where(collection.any().eq(pathBuilder))
.unique(collection.any().getDate(propName, LocalDate.class).max());
query.where(collection.any().getDate(propName, LocalDate.class).eq(sub));
}
}
That results in an IllegalArgumentException with: Undeclared path 'baseEntity_instructions_64058'. Add this path as a source to the query to be able to reference it.
Which I kinda understand, since I didn't actually declare 'baseEntity_instructions_64058' anywhere. But how am I supposed to do it?
When I then try to replace the collection.any() statement with entityPath:
DateSubQuery<LocalDate> sub =
new JPASubQuery().from(entityPath).where(entityPath.eq(pathBuilder))
.unique(entityPath.getDate(propName, LocalDate.class).max());
query.where(entityPath.getDate(propName, LocalDate.class).eq(sub));
The generated query looks not bad to me:
select baseEntity
from Person baseEntity
...
left join baseEntity.instructions
where baseEntity.instructions.validUntil = (select max(baseEntity.instructions.validUntil)
from baseEntity.instructions
where baseEntity.instructions = ?1)
..but throws an org.hibernate.QueryException with the following message:
illegal attempt to dereference collection [person0_.auto_id.instructions] with element property reference [validUntil]
Another attempt, with an alias which looks like this:
PathBuilder<IFlattenable> alias = new PathBuilder<IFlattenable>(entityClazz,entityPath.as("subEntity").toString());
query.leftJoin(alias);
DateSubQuery<LocalDate> sub = new JPASubQuery().from(entityPath).where(alias.eq(entityPath))
.unique(alias.getDate(propName, LocalDate.class).max());
query.where(alias.getDate(propName, LocalDate.class).eq(sub));
generated the following query:
select baseEntity
from person baseEntity
...
left join Set baseEntity.instructions as subEntity
where baseEntity.instructions as subEntity.validUntil = (select max(baseEntity.instructions as subEntity.validUntil)
from baseEntity.instructions
where baseEntity.instructions as subEntity = baseEntity.instructions)
which ends up with antlr.NoViableAltException: unexpected token: Set. I don't see why it adds the "Set" to the query. Also I would expect that "baseEntity.instructions as subEntity" is only used once (at the actual join) and after that only the alias "subEntity" for reference.
I have been stuck on this for a while now, read a lot of stackoverflow and github issue posts and tried a lot combinations of various "solutions". Is anybody able to help me out with this?
Thanks in advance, I will buy you a drink :)