1

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();
Chris Harris
  • 1,329
  • 4
  • 17
  • 28
  • I figured out where the trace.log file for WAS v8 is located. I'm able to see the queries. At least that's _some_ progress in the right direction. – Chris Harris Feb 07 '13 at 18:45
  • O.K. More progress has been made. I was able to navigate to HoursOfOperationPK via a Path object: `Path hopPk = hop.get( HoursOfOperation_.id ); cq.orderBy(cb.asc(hopPk.get(HoursOfOperationPK_.weekdayNumber)));` This causes a sort by two different HoursOfOperation alias's weekdayNumber and centerId fields in the generated SQL. What corrects the problem is if one of the HoursOfOperation aliases is only used to sort by weekdayNumber and the centerId sort is removed. How can I do this? – Chris Harris Feb 07 '13 at 19:23

1 Answers1

1

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.

perissf
  • 15,979
  • 14
  • 80
  • 117
  • You hit the nail on the head. It turns out that I don't even need either of those 2 Joins. I now see only 3 tables listed in the SELECT statement. I'm back to square one though. I'm not sure how to sort now. I can't change the Path to: `Path hop = center.get(Center_.hoursOfOperations);` and then continue down the path to the HoursOfOperationPK object. – Chris Harris Feb 07 '13 at 22:14