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();