From e9a6d6681d59750c438ddf384e23b8f28d971085 Mon Sep 17 00:00:00 2001 From: Jody Grassel Date: Tue, 11 Sep 2012 15:18:22 +0000 Subject: [PATCH] OPENJPA-2261: Query SQL Cache issue with NULL parameters git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@1383446 13f79535-47bb-0310-9956-ffa450edef68 --- .../persistence/querycache/QCEntity.java | 75 ++++++ .../querycache/TestQuerySQLCache.java | 222 ++++++++++++++++++ .../apache/openjpa/persistence/QueryImpl.java | 9 + 3 files changed, 306 insertions(+) create mode 100644 openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/querycache/QCEntity.java create mode 100644 openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/querycache/TestQuerySQLCache.java diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/querycache/QCEntity.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/querycache/QCEntity.java new file mode 100644 index 000000000..bba18975a --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/querycache/QCEntity.java @@ -0,0 +1,75 @@ +/* + * 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.querycache; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; + +@Entity +@NamedQueries({ + @NamedQuery(name = "QCEntity.getByAmount", query = "SELECT o from QCEntity o WHERE o.amount=:amount") +}) +public class QCEntity { + @Id + @Column(name = "PK") + private String pk; + + @Column(name = "DESCRIPTION") + private String description; + + @Column(name = "AMOUNT") + private Long amount; + + public QCEntity() { + + } + + public QCEntity(String pk, String description, Long amount) { + this.pk = pk; + this.description = description; + this.amount = amount; + } + + public String getPk() { + return pk; + } + + public void setPk(String pk) { + this.pk = pk; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Long getAmount() { + return amount; + } + + public void setAmount(Long amount) { + this.amount = amount; + } +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/querycache/TestQuerySQLCache.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/querycache/TestQuerySQLCache.java new file mode 100644 index 000000000..630945386 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/querycache/TestQuerySQLCache.java @@ -0,0 +1,222 @@ +/* + * 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.querycache; + +import java.util.List; + +import javax.persistence.EntityManager; +import javax.persistence.Query; + +import org.apache.openjpa.persistence.EntityManagerImpl; +import org.apache.openjpa.persistence.test.SQLListenerTestCase; + +public class TestQuerySQLCache extends SQLListenerTestCase { + EntityManager em; + + public void setUp() { + super.setUp( + DROP_TABLES, + "openjpa.jdbc.QuerySQLCache", "true", + "openjpa.DataCache", "false", + QCEntity.class + ); + em = emf.createEntityManager(); + + em.getTransaction().begin(); + QCEntity qc1 = new QCEntity("pk1", "description", Long.valueOf(1)); + QCEntity qc2 = new QCEntity("pk2", "description-2", Long.valueOf(1)); + QCEntity qc3 = new QCEntity("pk3", null, null); + + em.persist(qc1); + em.persist(qc2); + em.persist(qc3); + + em.getTransaction().commit(); + + em.clear(); + } + + public void testNullParamsWithNumericPosition01() { + // Verify Query SQL Cache is enabled + EntityManagerImpl eml = (EntityManagerImpl) em; + assertTrue(eml.getQuerySQLCache()); + + Query q = em.createQuery("SELECT o from QCEntity o WHERE o.amount=?1"); + + // Test with NULL parameter, SQL should contain a IS NULL predicate + resetSQL(); + q.setParameter(1, null); + List resultListNull1A = q.getResultList(); + assertTrue((getLastSQL(sql) != null) && (getLastSQL(sql).contains("IS NULL"))); + assertNotNull(resultListNull1A); + assertEquals(1, resultListNull1A.size()); + + resetSQL(); + q.setParameter(1, null); + List resultListNull1B = q.getResultList(); + assertTrue((getLastSQL(sql) != null) && (getLastSQL(sql).contains("IS NULL"))); + assertNotNull(resultListNull1B); + assertEquals(1, resultListNull1B.size()); + + // Test with non-NULL paramter, SQL should contain the = predicate + resetSQL(); + q.setParameter(1, new Long(1)); + List resultListNotNull = q.getResultList(); + assertTrue((getLastSQL(sql) != null) && !(getLastSQL(sql).contains("IS NULL"))); + assertNotNull(resultListNotNull); + assertEquals(2, resultListNotNull.size()); + + // Test again with NULL parameter, SQL should contain a IS NULL predicate + resetSQL(); + q.setParameter(1, null); + List resultListNull2 = q.getResultList(); + assertTrue((getLastSQL(sql) != null) && (getLastSQL(sql).contains("IS NULL"))); + assertNotNull(resultListNull2); + assertEquals(1, resultListNull2.size()); + } + + public void testNullParamsWithNumericPosition02() { + // Verify Query SQL Cache is enabled + EntityManagerImpl eml = (EntityManagerImpl) em; + assertTrue(eml.getQuerySQLCache()); + + // Test with NULL parameter, SQL should contain a IS NULL predicate + resetSQL(); + Query q1 = em.createQuery("SELECT o from QCEntity o WHERE o.amount=?1"); + q1.setParameter(1, null); + List resultListNull1A = q1.getResultList(); + assertTrue((getLastSQL(sql) != null) && (getLastSQL(sql).contains("IS NULL"))); + assertNotNull(resultListNull1A); + assertEquals(1, resultListNull1A.size()); + + resetSQL(); + Query q2 = em.createQuery("SELECT o from QCEntity o WHERE o.amount=?1"); + q2.setParameter(1, null); + List resultListNull1B = q2.getResultList(); + assertTrue((getLastSQL(sql) != null) && (getLastSQL(sql).contains("IS NULL"))); + assertNotNull(resultListNull1B); + assertEquals(1, resultListNull1B.size()); + + // Test with non-NULL paramter, SQL should contain the = predicate + resetSQL(); + Query q3 = em.createQuery("SELECT o from QCEntity o WHERE o.amount=?1"); + q3.setParameter(1, new Long(1)); + List resultListNotNull = q3.getResultList(); + assertTrue((getLastSQL(sql) != null) && !(getLastSQL(sql).contains("IS NULL"))); + assertNotNull(resultListNotNull); + assertEquals(2, resultListNotNull.size()); + + // Test again with NULL parameter, SQL should contain a IS NULL predicate + resetSQL(); + Query q4 = em.createQuery("SELECT o from QCEntity o WHERE o.amount=?1"); + q4.setParameter(1, null); + List resultListNull2 = q4.getResultList(); + assertTrue((getLastSQL(sql) != null) && (getLastSQL(sql).contains("IS NULL"))); + assertNotNull(resultListNull2); + assertEquals(1, resultListNull2.size()); + } + + public void testNullParamsWithNamedQuery01() { + // Verify Query SQL Cache is enabled + EntityManagerImpl eml = (EntityManagerImpl) em; + assertTrue(eml.getQuerySQLCache()); + + Query q = em.createNamedQuery("QCEntity.getByAmount"); + + resetSQL(); + q.setParameter("amount", null); + List resultListNull1A = q.getResultList(); + assertTrue((getLastSQL(sql) != null) && (getLastSQL(sql).contains("IS NULL"))); + assertNotNull(resultListNull1A); + assertEquals(1, resultListNull1A.size()); + em.clear(); + + // Test with NULL parameter, SQL should contain a IS NULL predicate + resetSQL(); + q.setParameter("amount", null); + List resultListNull1B = q.getResultList(); + assertTrue((getLastSQL(sql) != null) && (getLastSQL(sql).contains("IS NULL"))); + assertNotNull(resultListNull1B); + assertEquals(1, resultListNull1B.size()); + em.clear(); + + // Test with non-NULL parameter, SQL should contain the = predicate + resetSQL(); + q.setParameter("amount", new Long(1)); + List resultListNotNull = q.getResultList(); + assertTrue((getLastSQL(sql) != null) && !(getLastSQL(sql).contains("IS NULL"))); + assertNotNull(resultListNotNull); + assertEquals(2, resultListNotNull.size()); + em.clear(); + + // Test again with NULL parameter, SQL should contain a IS NULL predicate + resetSQL(); + q.setParameter("amount", null); + List resultListNull2 = q.getResultList(); + assertTrue((getLastSQL(sql) != null) && (getLastSQL(sql).contains("IS NULL"))); + assertNotNull(resultListNull2); + assertEquals(1, resultListNull2.size()); + em.clear(); + } + + public void testNullParamsWithNamedQuery02() { + // Verify Query SQL Cache is enabled + EntityManagerImpl eml = (EntityManagerImpl) em; + assertTrue(eml.getQuerySQLCache()); + + resetSQL(); + Query q1A = em.createNamedQuery("QCEntity.getByAmount"); + q1A.setParameter("amount", null); + List resultListNull1A = q1A.getResultList(); + assertTrue((getLastSQL(sql) != null) && (getLastSQL(sql).contains("IS NULL"))); + assertNotNull(resultListNull1A); + assertEquals(1, resultListNull1A.size()); + em.clear(); + + // Test with NULL parameter, SQL should contain a IS NULL predicate + resetSQL(); + Query q1B = em.createNamedQuery("QCEntity.getByAmount"); + q1B.setParameter("amount", null); + List resultListNull1B = q1B.getResultList(); + assertTrue((getLastSQL(sql) != null) && (getLastSQL(sql).contains("IS NULL"))); + assertNotNull(resultListNull1B); + assertEquals(1, resultListNull1B.size()); + em.clear(); + + // Test with non-NULL parameter, SQL should contain the = predicate + resetSQL(); + Query q2 = em.createNamedQuery("QCEntity.getByAmount"); + q2.setParameter("amount", new Long(1)); + List resultListNotNull = q2.getResultList(); + assertTrue((getLastSQL(sql) != null) && !(getLastSQL(sql).contains("IS NULL"))); + assertNotNull(resultListNotNull); + assertEquals(2, resultListNotNull.size()); + em.clear(); + + // Test again with NULL parameter, SQL should contain a IS NULL predicate + resetSQL(); + Query q3 = em.createNamedQuery("QCEntity.getByAmount"); + q3.setParameter("amount", null); + List resultListNull2 = q3.getResultList(); + assertTrue((getLastSQL(sql) != null) && (getLastSQL(sql).contains("IS NULL"))); + assertNotNull(resultListNull2); + assertEquals(1, resultListNull2.size()); + em.clear(); + } +} diff --git a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/QueryImpl.java b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/QueryImpl.java index e5cc0de5c..200186946 100644 --- a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/QueryImpl.java +++ b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/QueryImpl.java @@ -539,6 +539,15 @@ public class QueryImpl extends AbstractQuery implements Serializable { } return false; } + + // Determine if the query has NULL parameters. If so, then do not use a PreparedQuery from the cache + for (Object val : params.values()) { + if (val == null) { + ignorePreparedQuery(); + return false; + } + } + Boolean registered = cache.register(_id, _query, fetch); boolean alreadyCached = (registered == null); String lang = _query.getLanguage();