OPENJPA-660: SQL Query Cache supports binding non-primary key parameters to cached SQL. Commit fix on behalf of Fay Wang. Original test case developed by Vikram Bhatia.

git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@678722 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Pinaki Poddar 2008-07-22 11:54:26 +00:00
parent 4c0bf1be10
commit b4c557d0c0
9 changed files with 523 additions and 2 deletions

View File

@ -38,6 +38,7 @@ import org.apache.openjpa.jdbc.schema.ForeignKey;
import org.apache.openjpa.jdbc.sql.Joins;
import org.apache.openjpa.jdbc.sql.LogicalUnion;
import org.apache.openjpa.jdbc.sql.Result;
import org.apache.openjpa.jdbc.sql.SQLBuffer;
import org.apache.openjpa.jdbc.sql.Select;
import org.apache.openjpa.jdbc.sql.SelectExecutor;
import org.apache.openjpa.jdbc.sql.SelectImpl;
@ -466,7 +467,7 @@ public abstract class StoreCollectionFieldStrategy
SelectImpl sel = null;
Map<JDBCStoreManager.SelectKey, Object[]> storeCollectionUnionCache = null;
JDBCStoreManager.SelectKey selKey = null;
if (!((JDBCStoreManager)store).isQuerySQLCacheOn())
if (!((JDBCStoreManager)store).isQuerySQLCacheOn() || elems.length > 1)
union = newUnion(sm, store, fetch, elems, resJoins);
else {
parmList = new ArrayList();
@ -503,7 +504,7 @@ public abstract class StoreCollectionFieldStrategy
}
// only cache the union when elems length is 1 for now
if (!found && elems.length == 1) {
if (!found) {
Object[] objs1 = new Object[2];
objs1[0] = union;
objs1[1] = resJoins[0];
@ -531,6 +532,9 @@ public abstract class StoreCollectionFieldStrategy
sel.wherePrimaryKey(mapping, cols, cols, oid, store,
null, null, parmList);
List nonFKParams = sel.getSQL().getNonFKParameters();
if (nonFKParams != null && nonFKParams.size() > 0)
parmList.addAll(nonFKParams);
}
// create proxy

View File

@ -33,6 +33,7 @@ import org.apache.commons.lang.ObjectUtils;
import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration;
import org.apache.openjpa.jdbc.kernel.exps.Val;
import org.apache.openjpa.jdbc.schema.Column;
import org.apache.openjpa.jdbc.schema.ForeignKey;
import org.apache.openjpa.jdbc.schema.Sequence;
import org.apache.openjpa.jdbc.schema.Table;
import serp.util.Numbers;
@ -55,6 +56,7 @@ public final class SQLBuffer
private List _subsels = null;
private List _params = null;
private List _cols = null;
private List _nonFKParams = null;
/**
* Default constructor.
@ -145,6 +147,11 @@ public final class SQLBuffer
_cols.add(paramIndex, null);
}
}
if (buf._nonFKParams != null) {
if (_nonFKParams == null)
_nonFKParams = new ArrayList();
_nonFKParams.addAll(buf._nonFKParams);
}
}
public SQLBuffer append(Table table) {
@ -249,6 +256,26 @@ public final class SQLBuffer
_params.add(o);
if (_cols != null)
_cols.add(col);
if (col == null)
return this;
boolean isFK = false;
ForeignKey[] fks = col.getTable().getForeignKeys();
for (int i = 0; i < fks.length; i++) {
Column[] cols = fks[i].getColumns();
for (int j = 0; j < cols.length; j++) {
if (cols[j] == col) {
isFK = true;
break;
}
}
if (isFK)
break;
}
if (!isFK) {
if (_nonFKParams == null)
_nonFKParams = new ArrayList();
_nonFKParams.add(o);
}
}
return this;
}
@ -372,6 +399,9 @@ public final class SQLBuffer
return (_params == null) ? Collections.EMPTY_LIST : _params;
}
public List getNonFKParameters() {
return (_nonFKParams == null) ? Collections.EMPTY_LIST : _nonFKParams;
}
/**
* Return the SQL for this buffer.
*/

View File

@ -0,0 +1,83 @@
/*
* 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.jdbc.query.cache;
import java.util.ArrayList;
import java.util.Collection;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.apache.openjpa.persistence.jdbc.ElementClassCriteria;
/**
* Persistent entity with collection whose element type belongs to inheritance
* hierarchy mapped to a SINGLE_TABLE. Hence relationship loading will require
*
*/
@Entity
@Table(name = "DEPT")
@IdClass(DepartmentId.class)
public class Department {
@Id
private String name;
@OneToMany(mappedBy = "dept", cascade = CascadeType.PERSIST)
@ElementClassCriteria
private Collection<PartTimeEmployee> partTimeEmployees;
@OneToMany(mappedBy = "dept", cascade = CascadeType.PERSIST)
@ElementClassCriteria
private Collection<FullTimeEmployee> fullTimeEmployees;
public Collection<FullTimeEmployee> getFullTimeEmployees() {
return fullTimeEmployees;
}
public void addEmployee(FullTimeEmployee e) {
if (fullTimeEmployees == null)
fullTimeEmployees = new ArrayList<FullTimeEmployee>();
this.fullTimeEmployees.add(e);
e.setDept(this);
}
public Collection<PartTimeEmployee> getPartTimeEmployees() {
return partTimeEmployees;
}
public void addEmployee(PartTimeEmployee e) {
if (partTimeEmployees == null)
partTimeEmployees = new ArrayList<PartTimeEmployee>();
this.partTimeEmployees.add(e);
e.setDept(this);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,56 @@
/*
* 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.jdbc.query.cache;
import java.io.Serializable;
public class DepartmentId implements Serializable {
private String name;
public DepartmentId() {
this(null);
}
public DepartmentId(String name) {
this.name = name;
}
public int hashCode() {
return name.hashCode();
}
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof DepartmentId)) {
return false;
}
DepartmentId other = (DepartmentId) obj;
if (name == null) {
if (other.name != null) {
return false;
}
} else if (!name.equals(other.name)) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,59 @@
/*
* 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.jdbc.query.cache;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
@Entity
@Table(name = "EMP")
@IdClass(EmployeeId.class)
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "TYPE")
public abstract class Employee {
@Id
private String ssn;
@ManyToOne
private Department dept;
public String getSsn() {
return ssn;
}
public void setSsn(String ssn) {
this.ssn = ssn;
}
public Department getDept() {
return dept;
}
public void setDept(Department dept) {
this.dept = dept;
}
}

View File

@ -0,0 +1,56 @@
/*
* 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.jdbc.query.cache;
import java.io.Serializable;
public class EmployeeId implements Serializable {
private String ssn;
public EmployeeId(){
}
public EmployeeId(String ssn){
this.ssn = ssn;
}
public boolean equals (Object other)
{
if (other == this)
return true;
if (!(other instanceof EmployeeId))
return false;
EmployeeId obj = (EmployeeId) other;
if (ssn == null) {
if (obj.ssn != null) {
return false;
}
} else if (!ssn.equals(obj.ssn)) {
return false;
}
return (true);
}
public int hashCode ()
{
return (ssn.hashCode());
}
}

View File

@ -0,0 +1,38 @@
/*
* 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.jdbc.query.cache;
import javax.persistence.Column;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
@Entity
@DiscriminatorValue("F")
public class FullTimeEmployee extends Employee {
@Column(name = "salary")
private double salary;
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}

View File

@ -0,0 +1,38 @@
/*
* 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.jdbc.query.cache;
import javax.persistence.Column;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
@Entity
@DiscriminatorValue("P")
public class PartTimeEmployee extends Employee {
@Column(name = "wage")
private double hourlyWage;
public double getHourlyWage() {
return hourlyWage;
}
public void setHourlyWage(double hourlyWage) {
this.hourlyWage = hourlyWage;
}
}

View File

@ -0,0 +1,157 @@
/*
* 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.jdbc.query.cache;
import java.util.ArrayList;
import java.util.Collection;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import org.apache.openjpa.persistence.test.SQLListenerTestCase;
/**
* Tests that find() queries that use non-primary keys can be cached.
*
* SQL Query Cache caches SQL queries generated to select single entity.
* However, single instance queries may also join to other relations. Hence,
* primary key and foreign keys are normally the parameters to these queries
* which cached query binds again when being reused.
*
* The test verifies the case where non-primary keys are used as query
* parameters. The test employs a inheritance hierarchy mapped to SINGLE_TABLE.
* When derived instances are used in relationship, the discriminator values
* must be used in to join to the target type.
*
* For further details, refer <A
* HREF="https://issues.apache.org/jira/browse/OPENJPA-660">OPENJPA-660</A>
*
*
* @author Pinaki Poddar
* @author Vikram Bhatia
*
*/
public class TestNonPrimaryKeyQueryParameters extends SQLListenerTestCase {
private static final int FULLTIME_EMPLOYEE_COUNT = 3;
private static final int PARTTIME_EMPLOYEE_COUNT = 2;
private static final String DEPT_NAME = "ENGINEERING";
public void setUp() {
super.setUp(CLEAR_TABLES, Department.class, Employee.class,
FullTimeEmployee.class, PartTimeEmployee.class,
"openjpa.jdbc.QuerySQLCache", "true");
createDepartment(DEPT_NAME);
sql.clear();
}
public void testSelectQueryWithPrimaryKeyParameter() {
EntityManager em = emf.createEntityManager();
Query query = em
.createQuery("SELECT d from Department d where d.name=?1");
query.setParameter(1, DEPT_NAME);
Department dept = (Department) query.getSingleResult();
assertEquals(FULLTIME_EMPLOYEE_COUNT, dept.getFullTimeEmployees()
.size());
assertEquals(PARTTIME_EMPLOYEE_COUNT, dept.getPartTimeEmployees()
.size());
assertSQL(".* AND t0.TYPE = .*");
em.close();
}
public void testSelectQueryWithNoParameter() {
EntityManager em = emf.createEntityManager();
Query query = em.createQuery("SELECT d from Department d");
query.setParameter(1, DEPT_NAME);
Department dept = (Department) query.getSingleResult();
assertEquals(FULLTIME_EMPLOYEE_COUNT, dept.getFullTimeEmployees()
.size());
assertEquals(PARTTIME_EMPLOYEE_COUNT, dept.getPartTimeEmployees()
.size());
assertSQL(".* AND t0.TYPE = .*");
em.close();
}
public void testFind() {
EntityManager em = emf.createEntityManager();
Department dept = em.find(Department.class, DEPT_NAME);
assertEquals(FULLTIME_EMPLOYEE_COUNT, dept.getFullTimeEmployees()
.size());
assertEquals(PARTTIME_EMPLOYEE_COUNT, dept.getPartTimeEmployees()
.size());
assertSQL(".* AND t0.TYPE = .*");
em.close();
}
public void testSelectSubClass() {
EntityManager em = emf.createEntityManager();
Query query = em.createQuery("SELECT e from FullTimeEmployee e");
assertEquals(FULLTIME_EMPLOYEE_COUNT, query.getResultList().size());
query = em.createQuery("SELECT e from PartTimeEmployee e");
assertEquals(PARTTIME_EMPLOYEE_COUNT, query.getResultList().size());
assertSQL(".* WHERE t0.TYPE = .*");
}
public void testSelectBaseClass() {
EntityManager em = emf.createEntityManager();
Query query = em.createQuery("SELECT e from Employee e");
assertEquals(FULLTIME_EMPLOYEE_COUNT + PARTTIME_EMPLOYEE_COUNT, query
.getResultList().size());
assertNotSQL(".* WHERE t0.TYPE = .*");
}
private void createDepartment(String deptName) {
if (count(Department.class) > 0)
return;
Department dept = new Department();
dept.setName(deptName);
for (int i = 1; i <= FULLTIME_EMPLOYEE_COUNT; i++) {
FullTimeEmployee e = new FullTimeEmployee();
e.setSsn("888-PP-001" + i);
e.setSalary(100000);
dept.addEmployee(e);
}
for (int i = 1; i <= PARTTIME_EMPLOYEE_COUNT; i++) {
PartTimeEmployee e = new PartTimeEmployee();
e.setSsn("999-PP-001" + i);
e.setHourlyWage(20);
dept.addEmployee(e);
}
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
em.persist(dept);
em.getTransaction().commit();
em.close();
}
}