| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.apache.openjpa.persistence.criteria; |
| |
| import java.util.List; |
| |
| import javax.persistence.EntityManager; |
| import javax.persistence.Query; |
| import javax.persistence.TypedQuery; |
| import javax.persistence.criteria.CriteriaBuilder; |
| import javax.persistence.criteria.CriteriaQuery; |
| import javax.persistence.criteria.Root; |
| |
| import org.apache.openjpa.persistence.OpenJPAEntityManager; |
| import org.apache.openjpa.persistence.query.DomainObject; |
| import org.apache.openjpa.persistence.query.Expression; |
| import org.apache.openjpa.persistence.query.OpenJPAQueryBuilder; |
| import org.apache.openjpa.persistence.query.Predicate; |
| import org.apache.openjpa.persistence.query.QueryBuilderImpl; |
| import org.apache.openjpa.persistence.query.QueryDefinition; |
| import org.apache.openjpa.persistence.query.SelectItem; |
| import org.apache.openjpa.persistence.test.SingleEMFTestCase; |
| |
| |
| /** |
| * Tests QueryDefinition via a set of example use cases from Criteria API |
| * Section of Java Persistence API Version 2.0 [1]. |
| * |
| * For each use case, a corresponding JPQL String is specified. The dynamically |
| * constructed QueryDefinition and JPQL are both executed and their results |
| * are compared for verification. As some of the use cases employ few |
| * yet unimplemented JPQL 2.0 constructs such as KEY() or INDEX() or CASE, |
| * when such queries fail to execute, the JPQL String is literally compared |
| * to the stringified QueryDefinition. |
| * |
| * [1] <A href="http://jcp.org/aboutJava/communityprocess/pr/jsr317/index.html"> |
| * JPA API Specification Version 2.0</A> |
| * |
| * |
| */ |
| public class TestCriteria extends SingleEMFTestCase { |
| protected OpenJPAQueryBuilder qb; |
| protected StringComparison comparator = new StringComparison(); |
| |
| @Override |
| public void setUp() { |
| super.setUp(CLEAR_TABLES, |
| "openjpa.DynamicEnhancementAgent", "false", |
| "openjpa.DataCache","true", |
| "openjpa.QueryCache","true", |
| Account.class, |
| Address.class, |
| A.class, |
| B.class, |
| CompUser.class, |
| Contact.class, |
| Contractor.class, |
| Course.class, |
| CreditCard.class, |
| Customer.class, |
| C.class, |
| Department.class, |
| DependentId.class, |
| Dependent.class, |
| D.class, |
| Employee.class, |
| Exempt.class, |
| FemaleUser.class, |
| FrequentFlierPlan.class, |
| Item.class, |
| LineItem.class, |
| Magazine.class, |
| MaleUser.class, |
| Manager.class, |
| Movie.class, |
| Order.class, |
| Person.class, |
| Phone.class, |
| Photo.class, |
| Product.class, |
| Publisher.class, |
| Semester.class, |
| Student.class, |
| TransactionHistory.class, |
| Transaction.class, |
| VideoStore.class); |
| qb = (QueryBuilderImpl)emf.getDynamicQueryBuilder(); |
| emf.createEntityManager(); |
| } |
| |
| public void testLogicalPredicateAssociativity() { |
| DomainObject e = qb.createQueryDefinition(Employee.class); |
| Predicate p1 = e.get("salary").greaterThan(100); |
| Predicate p2 = e.get("rating").equal(5); |
| Predicate p3 = e.get("name").like("John"); |
| Predicate w1 = p1.and(p2.or(p3)); |
| Predicate w2 = (p1.and(p2)).or(p3); |
| QueryDefinition q1 = e.select(e).where(w1); |
| String jpql1 = qb.toJPQL(q1); |
| emf.createEntityManager().createDynamicQuery(q1).getResultList(); |
| |
| QueryDefinition q2 = e.select(e).where(w2); |
| String jpql2 = qb.toJPQL(q2); |
| System.err.println(jpql1); |
| System.err.println(jpql2); |
| assertNotEquals(jpql1, jpql2); |
| emf.createEntityManager().createDynamicQuery(q2).getResultList(); |
| } |
| |
| public void testMultipleDomainOfSameClass() { |
| DomainObject o1 = qb.createQueryDefinition(Order.class); |
| DomainObject o2 = o1.addRoot(Order.class); |
| o1.select(o1) |
| .where(o1.get("quantity").greaterThan(o2.get("quantity")) |
| .and(o2.get("customer").get("lastName").equal("Smith")) |
| .and(o2.get("customer").get("firstName").equal("John"))); |
| |
| String jpql = "select o from Order o, Order o2" + |
| " where o.quantity > o2.quantity" + |
| " and o2.customer.lastName = 'Smith'" + |
| " and o2.customer.firstName = 'John'"; |
| compare(jpql, o1); |
| } |
| |
| public void testFetchJoin() { |
| DomainObject d = qb.createQueryDefinition(Department.class); |
| d.leftJoinFetch("employees"); |
| d.where(d.get("deptNo").equal(1)); |
| |
| |
| String jpql = "select d from Department d" + |
| " LEFT JOIN FETCH d.employees" + |
| " where d.deptNo = 1"; |
| compare(jpql, d); |
| } |
| |
| public void testMultipartNavigation() { |
| DomainObject e = qb.createQueryDefinition(Employee.class); |
| DomainObject p = e.join("contactInfo").join("phones"); |
| e.where(e.get("contactInfo").get("address").get("zipCode") |
| .equal("95094")).select(p.get("vendor")); |
| |
| |
| String jpql = "select p.vendor from Employee e" + |
| " JOIN e.contactInfo c JOIN c.phones p" + |
| " where e.contactInfo.address.zipCode = '95094'"; |
| compare(jpql, e); |
| } |
| |
| public void testOperatorPath() { |
| QueryDefinition qdef = qb.createQueryDefinition(); |
| DomainObject item = qdef.addRoot(Item.class); |
| DomainObject photo = item.join("photos"); |
| qdef.select(item.get("name"), photo.value()) |
| .where(photo.key().like("egret")); |
| |
| |
| String jpql = "select i.name, VALUE(p)" |
| + " from Item i join i.photos p" |
| + " where KEY(p) like 'egret'"; |
| compare(jpql, qdef); |
| } |
| |
| public void testLiteral() { |
| DomainObject c = qb.createQueryDefinition(Customer.class); |
| DomainObject o = c.join("orders"); |
| DomainObject a = c.join("address"); |
| o.where(a.get("state").equal("CA").and( |
| a.get("county").equal("Santa Clara"))); |
| o |
| .select(o.get("quantity"), o.get("cost").times(1.08), a |
| .get("zipCode")); |
| |
| String jpql = "select o.quantity, o.cost*1.08, a.zipCode" + |
| " from Customer c join c.orders o join c.address a" + |
| " where a.state = 'CA' and a.county = 'Santa Clara'"; |
| compare(jpql, c); |
| } |
| |
| public void testTypeExpression() { |
| DomainObject e = qb.createQueryDefinition(Employee.class); |
| e.select(e.type()) |
| .where(e.type().equal(Exempt.class).not()); |
| |
| String jpql = "select TYPE(e)" + |
| " from Employee e" + |
| " where TYPE(e) <> Exempt"; |
| compare(jpql, e); |
| } |
| |
| public void testIndex() { |
| DomainObject c = qb.createQueryDefinition(Course.class); |
| DomainObject w = c.join("studentWaitList"); |
| c.where(c.get("name").equal("Calculus").and(w.index().equal(0))) |
| .select(w.get("name")); |
| |
| String jpql = "select s.name" + |
| " from Course c join c.studentWaitList s" + |
| " where c.name = 'Calculus' and INDEX(s) = 0"; |
| compare(jpql, c); |
| } |
| |
| public void testSum() { |
| DomainObject o = qb.createQueryDefinition(Order.class); |
| DomainObject l = o.join("lineItems"); |
| DomainObject c = o.join("customer"); |
| c.where(c.get("lastName").equal("Smith").and(c.get("firstName"). |
| equal("John"))).select(l.get("price").sum()); |
| |
| String jpql = "select SUM(l.price)" + |
| " from Order o join o.lineItems l JOIN o.customer c" + |
| " where c.lastName = 'Smith' and c.firstName = 'John'"; |
| compare(jpql, c); |
| } |
| |
| public void testSize() { |
| DomainObject d = qb.createQueryDefinition(Department.class); |
| d.where(d.get("name").equal("Sales")) |
| .select(d.get("employees").size()); |
| |
| String jpql = "select SIZE(d.employees)" + |
| " from Department d " + |
| " where d.name = 'Sales'"; |
| compare(jpql, d); |
| } |
| |
| public void testCount() { |
| |
| EntityManager em = emf.createEntityManager(); |
| em.getTransaction().begin(); |
| for(int i = 0;i<50;i++) |
| em.persist(new Department()); |
| em.getTransaction().commit(); |
| |
| CriteriaBuilder cb = em.getCriteriaBuilder(); |
| CriteriaQuery<Department> q = cb.createQuery(Department.class); |
| Root<Department> book = q.from(Department.class); |
| TypedQuery<Department> dept = em.createQuery(q); |
| int size = dept.getResultList().size(); |
| |
| CriteriaQuery<Long> c = cb.createQuery(Long.class); |
| Root<?> from = c.from(Department.class); |
| c.select(cb.count(from)); |
| TypedQuery<Long> query = em.createQuery(c); |
| long count = query.getSingleResult(); |
| assertEquals(size, count); |
| } |
| |
| public void testGeneralCase() { |
| DomainObject e = qb.createQueryDefinition(Employee.class); |
| e.where(e.get("department").get("name").equal("Engineering")); |
| e.select(e.get("name"), |
| e.generalCase() |
| .when(e.get("rating").equal(1)) |
| .then(e.get("salary").times(1.1)) |
| .when(e.get("rating").equal(2)) |
| .then(e.get("salary").times(1.2)) |
| .elseCase(e.get("salary").times(1.01))); |
| |
| String jpql = "SELECT e.name," |
| + " CASE WHEN e.rating = 1 THEN e.salary * 1.1" |
| + " WHEN e.rating = 2 THEN e.salary * 1.2" |
| + " ELSE e.salary * 1.01" |
| + " END" |
| + " FROM Employee e" |
| + " WHERE e.department.name = 'Engineering'"; |
| |
| compare(jpql, e); |
| } |
| |
| public void testMemberOf() { |
| DomainObject p = qb.createQueryDefinition(Person.class); |
| p.where(p.literal("Joe").member(p.get("nicknames"))); |
| |
| String jpql = "select p from Person p " + |
| " where 'Joe' MEMBER OF p.nicknames"; |
| compare(jpql, p); |
| } |
| |
| public void testParamater() { |
| QueryDefinition qdef = qb.createQueryDefinition(); |
| DomainObject customer = qdef.addRoot(Customer.class); |
| qdef.where(customer.get("status").equal(qdef.param("status"))); |
| |
| String jpql = "select c from Customer c " + |
| " where c.status = :status"; |
| compare(jpql, qdef, "status", 1); |
| } |
| |
| public void testBetween() { |
| DomainObject c = qb.createQueryDefinition(CreditCard.class); |
| DomainObject t = c.join("transactionHistory"); |
| c.select(t).where(c.get("holder").get("name").equal("John Doe") |
| .and(t.index().between(0, 9))); |
| |
| |
| String jpql = "select t from CreditCard c JOIN c.transactionHistory t" + |
| " where c.holder.name = 'John Doe' AND INDEX(t) " + |
| " BETWEEN 0 AND 9"; |
| |
| compare(jpql, c); |
| } |
| |
| public void testIsEmpty() { |
| DomainObject o = qb.createQueryDefinition(Order.class); |
| o.where(o.get("lineItems").isEmpty()); |
| |
| |
| String jpql = "select o from Order o " + |
| " where o.lineItems IS EMPTY"; |
| compare(jpql, o); |
| } |
| |
| public void testNonCorrelatedSubQuery() { |
| QueryDefinition q1 = qb.createQueryDefinition(); |
| DomainObject goodCustomer = q1.addRoot(Customer.class); |
| |
| QueryDefinition q2 = qb.createQueryDefinition(); |
| DomainObject customer = q2.addRoot(Customer.class); |
| |
| q1.where(goodCustomer.get("balanceOwned") |
| .lessThan(q2.select(customer.get("balanceOwned").avg()))); |
| |
| String jpql = "select c from Customer c " |
| + " where c.balanceOwned < " |
| + "(select AVG(c2.balanceOwned) from Customer c2)"; |
| compare(jpql, q1); |
| } |
| |
| public void testNew() { |
| QueryDefinition q = qb.createQueryDefinition(); |
| DomainObject customer = q.addRoot(Customer.class); |
| DomainObject order = customer.join("orders"); |
| q.where(order.get("count").greaterThan(100)) |
| .select(q.newInstance(Customer.class, customer.get("id"), |
| customer.get("status"), |
| order.get("count"))); |
| |
| |
| String jpql = |
| "SELECT NEW org.apache.openjpa.persistence.criteria.Customer" |
| + "(c.id, c.status, o.count)" |
| + " FROM Customer c JOIN c.orders o" + " WHERE o.count > 100"; |
| compare(jpql, q); |
| } |
| |
| public void testKeyValueOperatorPath() { |
| QueryDefinition q = qb.createQueryDefinition(); |
| DomainObject v = q.addRoot(VideoStore.class); |
| DomainObject i = v.join("videoInventory"); |
| q.where(v.get("location").get("zipCode").equal("94301").and( |
| i.value().greaterThan(0))); |
| q.select(v.get("location").get("street"), i.key().get("title"), i |
| .value()); |
| |
| String jpql = "SELECT v.location.street, KEY(v2).title, VALUE(v2)" |
| + " FROM VideoStore v JOIN v.videoInventory v2" |
| + " WHERE v.location.zipCode = '94301' AND VALUE(v2) > 0"; |
| |
| compare(jpql, q); |
| } |
| |
| public void testGroupByHaving() { |
| QueryDefinition q = qb.createQueryDefinition(); |
| DomainObject customer = q.addRoot(Customer.class); |
| q.select(customer.get("status"), customer.get("filledOrderCount").avg(), |
| customer.count()) |
| .groupBy(customer.get("status")) |
| .having(customer.get("status").in(1, 2)); |
| |
| String jpql = "SELECT c.status, AVG(c.filledOrderCount), COUNT(c)" |
| + " FROM Customer c" |
| + " GROUP BY c.status" |
| + " HAVING c.status IN (1, 2)"; |
| |
| compare(jpql, q); |
| } |
| |
| public void testGroupByHaving2() { |
| QueryDefinition q = qb.createQueryDefinition(); |
| DomainObject customer = q.addRoot(Customer.class); |
| q.select(customer.get("country"), customer.count()) |
| .groupBy(customer.get("country")) |
| .having(customer.count().greaterThan(30)); |
| |
| String jpql = "SELECT c.country, COUNT(c)" |
| + " FROM Customer c" |
| + " GROUP BY c.country" |
| + " HAVING COUNT(c) > 30"; |
| compare(jpql, q); |
| } |
| |
| public void testOrderBy() { |
| QueryDefinition q = qb.createQueryDefinition(); |
| DomainObject customer = q.addRoot(Customer.class); |
| DomainObject order = customer.join("orders"); |
| DomainObject address = customer.join("address"); |
| q.where(address.get("state").equal("CA")) |
| .select(order) |
| .orderBy(order.get("quantity").desc(), order.get("totalcost")); |
| String jpql = "SELECT o" |
| + " FROM Customer c JOIN c.orders o JOIN c.address a" |
| + " WHERE a.state = 'CA'" |
| + " ORDER BY o.quantity DESC, o.totalcost"; |
| compare(jpql, q); |
| } |
| |
| public void testOrderBy2() { |
| QueryDefinition q = qb.createQueryDefinition(); |
| DomainObject customer = q.addRoot(Customer.class); |
| DomainObject order = customer.join("orders"); |
| DomainObject address = customer.join("address"); |
| q.where(address.get("state").equal("CA")) |
| .select(order.get("quantity"), address.get("zipCode")) |
| .orderBy(order.get("quantity").desc(), address.get("zipCode")); |
| String jpql = "SELECT o.quantity, a.zipCode" |
| + " FROM Customer c JOIN c.orders o JOIN c.address a" |
| + " WHERE a.state = 'CA'" |
| + " ORDER BY o.quantity DESC, a.zipCode"; |
| compare(jpql, q); |
| } |
| |
| public void testOrderByExpression() { |
| DomainObject o = qb.createQueryDefinition(Order.class); |
| DomainObject a = o.join("customer").join("address"); |
| SelectItem taxedCost = o.get("cost").times(1.08); |
| o.select(o.get("quantity"), taxedCost, a.get("zipCode")) |
| .where(a.get("state").equal("CA") |
| .and(a.get("county").equal("Santa Clara"))) |
| .orderBy(o.get("quantity"), taxedCost, a.get("zipCode")); |
| |
| String jpql = "SELECT o.quantity, o.cost*1.08 as o2, a.zipCode" |
| + " FROM Order o JOIN o.customer c JOIN c.address a" |
| + " WHERE a.state = 'CA' AND a.county = 'Santa Clara'" |
| + " ORDER BY o.quantity, o2, a.zipCode"; |
| |
| compare(jpql, o); |
| } |
| |
| public void testCorrelatedSubquery() { |
| QueryDefinition q1 = qb.createQueryDefinition(); |
| DomainObject emp = q1.addRoot(Employee.class); |
| |
| QueryDefinition q2 = qb.createQueryDefinition(); |
| DomainObject spouseEmp = q2.addRoot(Employee.class); |
| |
| q2.where(spouseEmp.equal(emp.get("spouse"))).select(spouseEmp); |
| q1.selectDistinct(emp).where(q2.exists()); |
| |
| String jpql = "SELECT DISTINCT e " |
| + " FROM Employee e" |
| + " WHERE EXISTS (" |
| + " SELECT e2 " |
| + " FROM Employee e2" |
| + " WHERE e2 = e.spouse)"; |
| |
| compare(jpql, q1); |
| } |
| |
| public void testCreateSubquery() { |
| DomainObject customer = qb.createQueryDefinition(Customer.class); |
| DomainObject order = |
| qb.createSubqueryDefinition(customer.get("orders")); |
| customer.where(order.select(order.get("cost").avg()).greaterThan(100)); |
| |
| String jpql = "SELECT c " |
| + " FROM Customer c" |
| + " WHERE (SELECT AVG(o.cost) FROM c.orders o) > 100"; |
| |
| compare(jpql, customer); |
| } |
| |
| public void testTypeList() { |
| DomainObject q = qb.createQueryDefinition(Employee.class); |
| q.where(q.type().in(Exempt.class, Contractor.class)); |
| |
| String jpql = "SELECT e " |
| + " FROM Employee e" |
| + " WHERE TYPE(e) IN (Exempt, Contractor)"; |
| |
| compare(jpql, q); |
| } |
| |
| public void testStringList() { |
| DomainObject q = qb.createQueryDefinition(Customer.class); |
| q.where(q.get("country").in("USA", "UK", "France")); |
| |
| String jpql = "SELECT c " |
| + " FROM Customer c" |
| + " WHERE c.country IN ('USA', 'UK', 'France')"; |
| compare(jpql, q); |
| } |
| |
| public void testConcat() { |
| DomainObject e = qb.createQueryDefinition(Employee.class); |
| DomainObject f = e.join("frequentFlierPlan"); |
| Expression c = |
| e.generalCase().when(f.get("annualMiles").greaterThan(50000)).then( |
| "Platinum").when(f.get("annualMiles").greaterThan(25000)).then( |
| "Gold").elseCase("XYZ"); |
| e.select(e.get("name"), f.get("name"), e.concat(c, e |
| .literal("Frequent Flyer"))); |
| |
| String jpql = "SELECT e.name, f.name, CONCAT(" |
| + " CASE WHEN f.annualMiles > 50000 THEN 'Platinum'" |
| + " WHEN f.annualMiles > 25000 THEN 'Gold'" |
| + " ELSE 'XYZ' END, 'Frequent Flyer')" |
| + " FROM Employee e JOIN e.frequentFlierPlan f"; |
| |
| compare(jpql, e); |
| } |
| |
| public void testCorrelatedSubquerySpecialCase1() { |
| DomainObject o = qb.createQueryDefinition(Order.class); |
| DomainObject a = qb.createSubqueryDefinition(o.get("customer"). |
| get("accounts")); |
| o.select(o) |
| .where(o.literal(10000).lessThan(a.select(a.get("balance")).all())); |
| |
| String jpql = |
| "select o from Order o" + " where 10000 < ALL " |
| + " (select a.balance from o.customer c " |
| + "join o.customer.accounts a)"; |
| |
| compare(jpql, o); |
| } |
| |
| public void testCorrelatedSubquerySpecialCase2() { |
| DomainObject o = qb.createQueryDefinition(Order.class); |
| DomainObject c = o.join("customer"); |
| DomainObject a = qb.createSubqueryDefinition(c.get("accounts")); |
| o.select(o) |
| .where(o.literal(10000).lessThan(a.select(a.get("balance")).all())); |
| |
| String jpql = "select o from Order o JOIN o.customer c" |
| + " where 10000 < ALL " |
| + " (select a.balance from c.accounts a)"; |
| |
| compare(jpql, o); |
| } |
| |
| public void testRecursiveDefinitionIsNotAllowed() { |
| DomainObject q = qb.createQueryDefinition(Customer.class); |
| q.where(q.exists().and(q.get("name").equal("wrong"))); |
| |
| try { |
| qb.toJPQL(q); |
| fail(); |
| } catch (RuntimeException e) { |
| // good |
| } |
| } |
| |
| // --------------------------------------------------------------------- |
| // verification methods |
| // --------------------------------------------------------------------- |
| |
| /** |
| * Compare by executing the queries generated from the given JPQL and |
| * QueryDefinition. |
| */ |
| void compare(String jpql, QueryDefinition q) { |
| compare(jpql, q, (Object[])null); |
| } |
| |
| /** |
| * Compare hand crafted JPQL and QueryDefinition. |
| * If skip is null then execute both queries against the database, otherwise |
| * compare them literally. |
| */ |
| void compare(String jpql, QueryDefinition q, Object...p) { |
| executeActually(jpql, q, p); |
| } |
| |
| /** |
| * Compare the string version of QueryDefinition and given JPQL string with |
| * some flexibility of case-insensitive reserved words. |
| */ |
| private void compareLiterally(String jpql, QueryDefinition q) { |
| String actual = qb.toJPQL(q); |
| if (!comparator.compare(jpql,actual)) |
| fail("\r\nExpected: [" + jpql + "]\r\nActual : [" + actual + "]"); |
| } |
| |
| /** |
| * Executes the given JPQL and QueryDefinition independently and compare |
| * their results. |
| */ |
| private void executeActually(String jpql, QueryDefinition q, Object...p) { |
| OpenJPAEntityManager em = emf.createEntityManager(); |
| List<?> criteriaResult = null; |
| List<?> jpqlResult = null; |
| Throwable criteriaError = null; |
| Throwable jpqlError = null; |
| |
| try { |
| Query cq = em.createDynamicQuery(q); |
| setParameters(cq, p); |
| criteriaResult = cq.getResultList(); |
| } catch (Exception e) { |
| criteriaError = e; |
| } |
| try { |
| Query nq = em.createQuery(jpql); |
| setParameters(nq, p); |
| jpqlResult = nq.getResultList(); |
| } catch (Exception e) { |
| jpqlError = e; |
| } |
| |
| if (criteriaError == null && jpqlError == null) { |
| assertEquals(criteriaResult.size(), jpqlResult.size()); |
| } else if (criteriaError != null && jpqlError == null) { |
| fail("QueryDefinition generated invalid JPQL\r\n" |
| + "Criteria [" + qb.toJPQL(q) + "]\r\n" |
| + "error : " + criteriaError.getMessage()); |
| } else if (criteriaError == null && jpqlError != null) { |
| fail("Handcrafted JPQL is invalid \r\n" |
| + "JPQL [" + jpql + "]\r\n" |
| + "error : " + jpqlError.getMessage()); |
| } else { |
| compareLiterally(jpql, q); |
| } |
| } |
| void setParameters(Query q, Object...p) { |
| if (p == null) |
| return; |
| for (int i = 0; i < p.length; i += 2) { |
| q.setParameter(p[i].toString(), p[i+1]); |
| } |
| } |
| } |
| |