JPA 2 + Criteria API - Определение подзапроса - PullRequest
5 голосов
/ 13 января 2012

Я пытаюсь преобразовать SQL-запрос в Criteria API, но пока безуспешно. Я могу создать два отдельных запроса, которые возвращают нужные мне значения, но я не знаю, как объединить их в один запрос.

Вот SQL-оператор, который работает:

select company.*, ticketcount.counter from company
    join 
(select company, COUNT(*) as counter from ticket where state<16 group by company) ticketcount
on company.compid = ticketcount.company;

Этот критерий запроса возвращает результаты внутреннего запроса:

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<intCompany> qTicket = cb.createQuery(intCompany.class);
Root<Ticket> from = qTicket.from(Ticket.class);
Path groupBy = from.get("company");        
Predicate state = cb.notEqual(from.<State>get("state"), getStateById(16));        
qTicket.select(cb.construct(
    intCompany.class, cb.count(from),from.<Company>get("company")))
        .where(state).groupBy(groupBy);                
em.createQuery(qTicket).getResultList();

В приложении я определил небольшой класс оболочки / помощника:

public  class intCompany{
    public Company comp;
    public Long opentickets;
    public intCompany(Long opentickets,Company comp){
        this.comp = comp;
        this.opentickets = opentickets;
    }
    public intCompany(){

    }
}

Так у кого-нибудь есть идеи, как заставить это работать?

Обновление

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

List<intCompany> result = em.createQuery(cq).getResultList();
List<Company> cresult = new ArrayList();
    for(intCompany ic: result){
        ic.comp.setOpentickets(ic.opentickets.intValue());
        cresult.add(ic.comp);
    }
return cresult;

Может быть, просто невозможно преобразовать исходный sql в Criteria API.

Еще одно обновление

Я понял, что мне нужно изменить исходное выражение sql на

select company.*, ticketcount.counter from company
    left join 
(select company, COUNT(*) as counter from ticket where state<16 group by company) ticketcount
on company.compid = ticketcount.company;

В противном случае я не получаю компаний без записей в таблице заявок.

Так есть ли другие предложения?

Ответы [ 2 ]

2 голосов
/ 13 февраля 2013

У меня была похожая проблема. Моим решением было использовать левые внешние соединения.

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Entity> query = cb.createQuery(Entity.class);
Root<Entity> root = query.from(Entity.class);

Join<Entity,ChildEntity> join = root.join(Entity_.children, JoinType.LEFT);
query.groupBy(root.get( Entity_.id ));
query.select(
  cb.construct(
    EntityDTO.class,
    root.get( Entity_.id ),
    root.get( Entity_.name ),
    cb.count(join)
));

Это JoinType.LEFT гарантирует, что вы получите записи Entity (компании), даже если у них нет дочерних объектов (билетов).

Класс сущности:

@Entity
public class Entity {
    ...

    @OneToMany(targetEntity = ChildEntity.class, mappedBy = "parent", fetch = FetchType.LAZY, orphanRemoval = false)
    private Set<ChildEntity> objects;

    ...
}

Статическая модель:

@StaticMetamodel( Entity.class )
public class Entity_ {
    public static volatile SingularAttribute<Entity, Long> id;
    public static volatile SingularAttribute<Entity, String> name;
    ...
    public static volatile SetAttribute<Entity, ChildEntity> objects;
}
2 голосов
/ 13 января 2012

У вас почти все сделано.

//---//
CriteriaBuilder cb = em.getCriteriaBuilder();
//Your Wrapper class constructor must match with multiselect arguments
CriteriaQuery<IntCompany> cq = cb.createQuery(IntCompany.class);
//Main table
final Root<Ticket> fromTicket= cq.from(Ticket.class);
//Join defined in Ticket Entity
final Path company = fromTicket.get("company");
//Data to select
cq.multiselect(cb.count(from), company);
//Grouping
cq.groupBy(company);
//Restrictions (I don't really understand what you're querying)
Predicate p = cb.lessThan(fromTicket.get("state"), 16);
//You can add more restrictions
// p = cb.and/or(p, ...);
cq.where(p);
List<IntCompany> results = entityManager.createQuery(cq).getResultList();

Это должно работать как положено.

...