Question

I'm trying to replicate the following, working query using the Criteria Query API + RAD/Dali-auto-generated static canonical metamodels for OpenJPA 2.1.2-SNAPSHOT on WebSphere v8.0.0.5:

`SELECT *
FROM CENTER c
    INNER JOIN STATE s ON s.ID = c.STATE_ID
    INNER JOIN HOURS_OF_OPERATION h ON h.CENTER_ID = c.ID
WHERE c.CITY = '<city_name_here>'
ORDER BY h.WEEKDAY_NUMBER;`

I have four entities that form the core of this query:

  • Center.java
  • State.java
  • HoursOfOperation.java
  • HoursOfOperationPK.java

There are many Centers per State. There are many HoursOfOperations per Center. I need to return a result set comprised of center info, the state abbreviation, and the center's hours of operation sorted ASC by the weekday numbers 1-7, which represent Monday - Sunday.

Here's my method:

public Center getCenterInfo(String centerCityName) {
    CriteriaBuilder cb = em.getCriteriaBuilder();

    CriteriaQuery<Center> cq = cb.createQuery(Center.class);
    Metamodel m = em.getMetamodel();
    EntityType<Center> _center = m.entity(Center.class);
    EntityType<State> _state = m.entity(State.class);

    Root<Center> center = cq.from( _center );
    Join<Center, State> state = center.join( Center_.state );
    Join<Center, HoursOfOperation> hop = center.join( Center_.hoursOfOperations );
    cq.select(center).distinct(true);
    Predicate predicate = cb.equal(center.get(Center_.city), centerCityName);
    cq.where(predicate);
    //cq.orderBy(cb.asc(hop.get(HoursOfOperation_.id)));<---Can't access PK field here
    center.fetch( Center_.state );
    center.fetch( Center_.hoursOfOperations );

    TypedQuery<Center> query = em.createQuery( cq );
    Center centerInfo = query.getSingleResult();

    return centerInfo;
}

I've commented out the line that I'm stuck on. I would think that I have to call some method to instantiate some kind of HoursOfOperationPK instance, much like how I used Join<Center, HoursOfOperation> hop. I would think that doing so would allow me to use something like cq.orderBy(cb.asc(hopPk.get(HoursOfOperationPK_.weekdayNumber))); How can I achieve this sort?

Secondly, if I don't use cq.select(center).distinct(true);, I get back 49 records instead of 7 records. Why is that? The only thing that trims the record count down to 7 is a distinct method appended to the select. I understand what DISTINCT does in SQL, but my ANSI-style SQL syntax up top returns only 7 records.

The OpenJPA log output indicates that an OrderBy is applied to HoursOfOperation.centerId.

Here's the relevant parts of the HoursOfOperation and HoursOfOperationPK entities:

@Entity
@Table(name="HOURS_OF_OPERATION")
public class HoursOfOperation implements Serializable {
    private static final long serialVersionUID = 1L;

    @EmbeddedId
    private HoursOfOperationPK id;

    public HoursOfOperationPK getId() {
        return this.id;
    }

    public void setId(HoursOfOperationPK id) {
        this.id = id;
    }
}

@Embeddable
public class HoursOfOperationPK implements Serializable {
    //default serial version id, required for serializable classes.
    private static final long serialVersionUID = 1L;

    @Column(name="CENTER_ID", unique=true, nullable=false)
    private long centerId;

    @Column(name="WEEKDAY_NUMBER", unique=true, nullable=false)
    private long weekdayNumber;

    public HoursOfOperationPK() {
    }
    public long getCenterId() {
        return this.centerId;
    }
    public void setCenterId(long centerId) {
        this.centerId = centerId;
    }
    public long getWeekdayNumber() {
        return this.weekdayNumber;
    }
    public void setWeekdayNumber(long weekdayNumber) {
        this.weekdayNumber = weekdayNumber;
    }
}

EDIT @perissf I was able to generate the desired outcome sans the explicit order by ASC using (a sort seems to implicitly occur due to weekdayNumber being part of a compound primary key for the Hours of Operations table. I'd rather have the explicit sort though, since it could help me for other queries where I may not be so lucky):

    CriteriaBuilder cb = em.getCriteriaBuilder();

    CriteriaQuery<Center> cq = cb.createQuery(Center.class);
    Metamodel m = em.getMetamodel();
    EntityType<Center> _center = m.entity(Center.class);

    Root<Center> center = cq.from( _center );
    Expression<List<HoursOfOperation>> hop = center.get( Center_.hoursOfOperations );
    cq.select(center);
    Predicate predicate = cb.equal(center.get(Center_.city), centerCityName);
    cq.where(predicate);

    center.fetch( Center_.state );
    center.fetch( Center_.hoursOfOperations );

    TypedQuery<Center> query = em.createQuery( cq );
    Center centerInfo = query.getSingleResult();

However, I was able to also generate the desired SQL using the following, but the only problem is that the center's hoursOfOperation was not set (Due to lazy loading. A center.Fetch(Center_.hoursOfOperation) created duplicate records. Anyone have a solution?):

    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery<Center> cq = cb.createQuery(Center.class);
    Metamodel m = em.getMetamodel();
    EntityType<Center> _center = m.entity(Center.class);
    Root<Center> center = cq.from( _center );
    EntityType<HoursOfOperation> _hoo = m.entity(HoursOfOperation.class);
    Root<HoursOfOperation> hoo = cq.from( _hoo );
    cq.select(center).distinct(true);
    Predicate predicate = cb.and(cb.equal(center.get(Center_.city), centerCityName), 
            cb.equal(center, hoo.get(HoursOfOperation_.center)));
    cq.where(predicate);
    cq.orderBy(cb.asc(hoo.get( HoursOfOperation_.id ).get(HoursOfOperationPK_.weekdayNumber)));
    center.fetch( Center_.state );

    TypedQuery<Center> query = em.createQuery( cq );
    Center centerInfo = query.getSingleResult();
Was it helpful?

Solution

I have tested with EclipseLink + MySql, and can confirm that the following code generates the query you are looking for:

Root<Center> center = cq.from(Center.class);
Join<Center, HoursOfOperation> hoursOfOperation = center.join(Center_.hoursOfOperations);
Join<Center, State> state = center.join(Center_.stateId);
Path<HoursOfOperationPK> hoursOfOperationPK = hoursOfOperation.get(HoursOfOperation_.hoursOfOperationPK);
cq.where(cb.equal(center.get(Center_.city), "cityName"));
cq.orderBy(cb.asc(hoursOfOperationPK.get(HoursOfOperationPK_.weekdayNumber)));
TypedQuery<Center> query = em.createQuery(cq);

Resulting query:

SELECT t1.id, t1.city, t1.state_id 
FROM state t0, hours_of_operation t2, center t1 
WHERE ((t1.city = ?) AND ((t0.id = t1.state_id) AND (t2.center_id = t1.id))) 
ORDER BY t2.weekday_number ASC

If the resulting rows are too many compared to what you were expecting, this is due to the join with the State entity, which isn't filtered by any where Predicate, so the resulting rows are the carthesian product of the two tables.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top