Многоуровневый подзапрос с JPA CriteriaBuilder - PullRequest
0 голосов
/ 28 ноября 2018

У меня есть следующие объекты JPA

@Entity
@Table(name="application_user")
public class ApplicationUser {
    @Id
    @Column(name="user_id")
    private String userid;

    @Column(name="last_write_time")
    private Instant lastWrite;

    //other fields omitted
}

@Entity
@Table(name="demographic")
public class Demographic {
    @Id
    @Column(name="user_id")
    private String userid;

    //primary key is a foreign key link
    @OneToOne
    @PrimaryKeyJoinColumn(name="user_id", referencedColumnName="user_id")
    private ApplicationUser user;

    //other fields omitted
}

Моя цель - получить всю демографию, содержащую пользователей, где время последней записи является максимальным значением в столбце.Я очень хочу написать следующий SQL с использованием JPA CriteriaBUilder

select * from demographic where
  userid in (
    select userid from application_user where
      last_write in (
        select max(last_write) from application_user
      )
  )

Я попытался написать следующий код CriteriaBuilder для достижения этой цели, и он успешно компилируется.Примечание. Я использую сгенерированные классы метамодели.

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Demographic> c = cb.createQuery(Demographic.class);
Root<Demographic> root = c.from(Demographic.class);
root.fetch(Demographic_.user, JoinType.INNER);

Subquery<Instant> sqLatestUsers = c.subquery(Instant.class);
Root<ApplicationUser> subRootLatestUsers = sqLatestUsers.from(ApplicationUser.class);
sqLatestUsers.select(cb.greatest(subRootLatestUsers.<Instant>get(ApplicationUser_.LAST_WRITE)));
Predicate predicateLatestUsers = subRootLatestUsers.get(ApplicationUser_.LAST_WRITE).in(sqLatestUsers);

Subquery<ApplicationUser> sq = c.subquery(ApplicationUser.class);
Root<Demographic> subRoot = sq.from(Demographic.class);
sq.select(subRoot.<ApplicationUser>get(Demographic_.USER)).where(predicateLatestUsers);
Predicate containsUsers = subRoot.get(Demographic_.USER).in(sq);

c.select(root).where(containsUsers);

Код компилируется и успешно развертывается в Wildfly 14, но при выполнении кода выдается следующая ошибка (с пробелами для улучшения читабельности):

Invalid path: 'generatedAlias2.user' :  Invalid path: 'generatedAlias2.user'
...
Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: Invalid path: 'generatedAlias2.user' [
select generatedAlias0 from com.company.model.Demographic as generatedAlias0 
  inner join fetch generatedAlias0.user as generatedAlias1 
    where generatedAlias2.user in (
      select generatedAlias2.user from com.company.model.Demographic as generatedAlias2 where generatedAlias3.lastWrite in (
        select max(generatedAlias3.lastWrite) from com.company.model.StarfishUser as generatedAlias3
      )
    )
]

Разрешены ли в спецификации JPA цепочки подзапросов (вложенных подзапросов)?Я нашел что-то, что синтаксически правильно, но на самом деле не разрешено?

1 Ответ

0 голосов
/ 30 ноября 2018

Я выясняю, как заставить работать подзапрос.Во-первых, мой обновленный метод Utility

public static <R, T> Subquery<T> getLatestSubelement(CriteriaBuilder cb, CriteriaQuery<R> c, Class<T> clazz, SingularAttribute<T, Instant> attribute) {
    //Get latest timestamp
    Subquery<Instant> sq = c.subquery(Instant.class);
    Root<T> subRoot = sq.from(clazz);
    sq.select(cb.greatest(subRoot.<Instant>get(attribute)));

    //Get object with the latest timestamp
    Subquery<T> sq2 = c.subquery(clazz);
    Root<T> subRoot2 = sq2.from(clazz);
    sq2.where(subRoot2.get(attribute).in(sq));
    return sq2;
}

Вот код, который использует метод утилит

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Demographic> c = cb.createQuery(Demographic.class);
Root<Demographic> root = c.from(Demographic.class);
joinType = JoinType.INNER;
//use fetch instead of join to prevent duplicates in Lists
root.fetch(Demographic_.user, joinType);

Subquery<ApplicationUser> sq = JpaUtil.getLatestSubelement(cb, c, ApplicationUser.class, ApplicationUser_.lastWrite);
c.where(root.get(Demographic_.user).in(sq));
TypedQuery<Demographic> q = em.createQuery(c);
Stream<Demographic> stream = q.getResultStream();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...