This shows examples on how to use EclipseLink to define queries on inheritance hierarchies with down casting to specific classes.
JPA/ORM developers will use this query feature to query across attributes in subclasses. This feature is available in JPQL, EclipseLink Expressions and Criteria API.
Extensions to the expression framework to limit the results to those of a specific subclass have already been implemented as part of the JPA 2.0 effort. Expression.type(Class) is available in the expression framework and equivalent functionality is available in JPQL.
e.g. "select p from Employee e join e.projects p where type(p) = LargeProject" can be used to retrieve all the LargeProjects (Subclass of Project) from Employee.
JPQL Extensions to use Downcast
JPQL is extended to cast in the FROM clause. The format of this will use the keyword "TREAT" and be part of the join clause. The following is an example:
select e from Employee e join TREAT(e.projects AS LargeProject) lp where lp.budget = value
JPA Criteria API already provides a casting operator. It is Expression.as(type).
As it is defined by JPA 2.0, Expression.as(type) does a simple cast that allows matching of types within the generics.
EclipseLink 2.1 extends criteria API to allow a cast using Expression.as(type). The as method has been extended to check the hierarchy and if type is a subclass of the type for the expression that as is being called on a cast will be implemented. Here is a criteria query that will do a cast:
Root<Employee> empRoot = cq1.from(getEntityManagerFactory().getMetamodel().entity(Employee.class)); Join<Employee, Project> join = empRoot.join("projects"); Path exp = ((Path)join.as(LargeProject.class)).get("budget"); cq1.where(qb1.equal(exp, new Integer(1000)) );
Calling a cast on a JOIN node will permanently alter that node. i.e. In the example above, after calling join.as(LargeProject.class), join will refer to a LargeProject.
EclipseLink Expression Support for Downcast
We will implement Expression.as(Class). The following is an example of how one could use it:
ReadAllQuery raq = new ReadAllQuery(Employee.class); Expression criteria = raq.getExpressionBuilder().anyOf("projects").as(LargeProject.class).get("budget").greaterThan(100); raq.setSelectionCriteria(criteria); List resultList = query.getResultList();
In this query Employee has a xToMany mapping to Project. LargeProject is a subclass of Project and the "budget" attribute is contained on LargeProject.
The following query:
Select e from Employee e join e.projects project
Will currently produce the following sql:
SELECT <select list> FROM CMP3_EMPLOYEE t1 LEFT OUTER JOIN CMP3_DEPT t0 ON (t0.ID = t1.DEPT_ID), CMP3_EMP_PROJ t4, CMP3_PROJECT t3, CMP3_SALARY t2 WHERE ((t2.EMP_ID = t1.EMP_ID) AND ((t4.EMPLOYEES_EMP_ID = t1.EMP_ID) AND (t3.PROJ_ID = t4.projects_PROJ_ID)))
Expression criteria = project.as(LargeProject.class).get("budget").greaterThan(100); raq.setSelectionCriteria(criteria);
The following SQL will be produced:
SELECT <select list> FROM CMP3_PROJECT t3 LEFT OUTER JOIN CMP3_LPROJECT t4 ON (t4.PROJ_ID = t3.PROJ_ID),CMP3_EMPLOYEE t1 LEFT OUTER JOIN CMP3_DEPT t0 ON (t0.ID = t1.DEPT_ID), CMP3_EMP_PROJ t5, CMP3_SALARY t2 WHERE (((t4.BUDGET > ?) AND (t2.EMP_ID = t1.EMP_ID)) AND ((t5.EMPLOYEES_EMP_ID = t1.EMP_ID) AND (t3.PROJ_ID = t5.projects_PROJ_ID))) bind => [100.0]
The changes as listed above in bold.