From db8ef80982b6c0ce9d4e3052a7b183ccddf86960 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Mon, 3 Oct 2011 15:08:40 -0700 Subject: [PATCH] HHH-4838 : Immutable natural key lookup not recognized using query cache --- .../java/org/hibernate/loader/Loader.java | 13 +- .../test/annotations/naturalid/A.java | 89 +++++ .../test/annotations/naturalid/D.java | 70 ++++ .../ImmutableNaturalKeyLookupTest.java | 323 ++++++++++++++++++ 4 files changed, 493 insertions(+), 2 deletions(-) create mode 100644 hibernate-testsuite/src/test/java/org/hibernate/test/annotations/naturalid/A.java create mode 100644 hibernate-testsuite/src/test/java/org/hibernate/test/annotations/naturalid/D.java create mode 100644 hibernate-testsuite/src/test/java/org/hibernate/test/annotations/naturalid/ImmutableNaturalKeyLookupTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/loader/Loader.java b/hibernate-core/src/main/java/org/hibernate/loader/Loader.java index e4c28121c5..805c7cda90 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/Loader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/Loader.java @@ -2342,8 +2342,13 @@ public abstract class Loader { List result = null; if ( session.getCacheMode().isGetEnabled() ) { - boolean isImmutableNaturalKeyLookup = queryParameters.isNaturalKeyLookup() - && getEntityPersisters()[0].getEntityMetamodel().hasImmutableNaturalId(); + boolean isImmutableNaturalKeyLookup = + queryParameters.isNaturalKeyLookup() && + resultTypes.length == 1 && + resultTypes[0].isEntityType() && + getEntityPersister( EntityType.class.cast( resultTypes[0] ) ) + .getEntityMetamodel() + .hasImmutableNaturalId(); final PersistenceContext persistenceContext = session.getPersistenceContext(); boolean defaultReadOnlyOrig = persistenceContext.isDefaultReadOnly(); @@ -2400,6 +2405,10 @@ public abstract class Loader { return result; } + private EntityPersister getEntityPersister(EntityType entityType) { + return factory.getEntityPersister( entityType.getAssociatedEntityName() ); + } + private void putResultInQueryCache( final SessionImplementor session, final QueryParameters queryParameters, diff --git a/hibernate-testsuite/src/test/java/org/hibernate/test/annotations/naturalid/A.java b/hibernate-testsuite/src/test/java/org/hibernate/test/annotations/naturalid/A.java new file mode 100644 index 0000000000..af1b8f7f3d --- /dev/null +++ b/hibernate-testsuite/src/test/java/org/hibernate/test/annotations/naturalid/A.java @@ -0,0 +1,89 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2011, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.annotations.naturalid; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Version; + +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.annotations.NaturalId; + +/** + * @author Guenther Demetz + */ +@Entity +@Cache(usage = CacheConcurrencyStrategy.READ_WRITE) +public class A { + + @Id + @GeneratedValue(strategy = GenerationType.TABLE) + private long oid; + + @Version + private int version; + + @Column + @NaturalId(mutable = false) + private String name; + + @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) + @org.hibernate.annotations.OptimisticLock(excluded = true) + @javax.persistence.OneToMany(mappedBy = "a") + private Set ds = new HashSet(); + + @javax.persistence.OneToOne + private D singleD = null; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getDs() { + return ds; + } + + public void setDs(Set ds) { + this.ds = ds; + } + + public D getSingleD() { + return singleD; + } + + public void setSingleD(D singleD) { + this.singleD = singleD; + } + +} diff --git a/hibernate-testsuite/src/test/java/org/hibernate/test/annotations/naturalid/D.java b/hibernate-testsuite/src/test/java/org/hibernate/test/annotations/naturalid/D.java new file mode 100644 index 0000000000..1841890555 --- /dev/null +++ b/hibernate-testsuite/src/test/java/org/hibernate/test/annotations/naturalid/D.java @@ -0,0 +1,70 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2011, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.annotations.naturalid; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Version; + +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; + +/** + * @author Guenther Demetz + */ +@Entity +@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL) +public class D { + @Id + @GeneratedValue(strategy = GenerationType.TABLE) + public long oid; + + @Version + private int version; + + @javax.persistence.ManyToOne(fetch = FetchType.LAZY) + private A a = null; + + @javax.persistence.OneToOne(mappedBy = "singleD") + private A singleA = null; + + public A getA() { + return a; + } + + public void setA(A a) { + this.a = a; + } + + public A getSingleA() { + return singleA; + } + + public void setSingleA(A singleA) { + this.singleA = singleA; + } +} diff --git a/hibernate-testsuite/src/test/java/org/hibernate/test/annotations/naturalid/ImmutableNaturalKeyLookupTest.java b/hibernate-testsuite/src/test/java/org/hibernate/test/annotations/naturalid/ImmutableNaturalKeyLookupTest.java new file mode 100644 index 0000000000..a1edd05e03 --- /dev/null +++ b/hibernate-testsuite/src/test/java/org/hibernate/test/annotations/naturalid/ImmutableNaturalKeyLookupTest.java @@ -0,0 +1,323 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2011, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.annotations.naturalid; + +import org.hibernate.Criteria; +import org.hibernate.FetchMode; +import org.hibernate.FlushMode; +import org.hibernate.Session; +import org.hibernate.Transaction; +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; +import org.hibernate.criterion.Restrictions; +import org.hibernate.sql.JoinFragment; +import org.hibernate.test.annotations.TestCase; + +/** + * @author Guenther Demetz + */ +public class ImmutableNaturalKeyLookupTest extends TestCase { + + public void testSimpleImmutableNaturalKeyLookup() { + Session s = openSession(); + Transaction newTx = s.getTransaction(); + + newTx.begin(); + A a1 = new A(); + a1.setName( "name1" ); + s.persist( a1 ); + newTx.commit(); + + newTx = s.beginTransaction(); + getCriteria( s ).uniqueResult(); // put query-result into cache + A a2 = new A(); + a2.setName( "xxxxxx" ); + s.persist( a2 ); + newTx.commit(); // Invalidates space A in UpdateTimeStamps region + + newTx = s.beginTransaction(); + + // please enable + // log4j.logger.org.hibernate.cache.StandardQueryCache=DEBUG + // log4j.logger.org.hibernate.cache.UpdateTimestampsCache=DEBUG + // to see that isUpToDate is called where not appropriated + + assertTrue( s.getSessionFactory().getStatistics().isStatisticsEnabled() ); + s.getSessionFactory().getStatistics().clear(); + + getCriteria( s ).uniqueResult(); // should produce a hit in StandardQuery cache region + + assertEquals( + "query is not considered as isImmutableNaturalKeyLookup, despite fullfilling all conditions", + 1, s.getSessionFactory().getStatistics().getQueryCacheHitCount() + ); + + s.createQuery( "delete from A" ).executeUpdate(); + newTx.commit(); + // Shutting down the application + s.close(); + } + + public void testNaturalKeyLookupWithConstraint() { + Session s = openSession(); + Transaction newTx = s.getTransaction(); + + newTx.begin(); + A a1 = new A(); + a1.setName( "name1" ); + s.persist( a1 ); + newTx.commit(); + + newTx = s.beginTransaction(); + getCriteria( s ).add( Restrictions.isNull( "singleD" ) ).uniqueResult(); // put query-result into cache + A a2 = new A(); + a2.setName( "xxxxxx" ); + s.persist( a2 ); + newTx.commit(); // Invalidates space A in UpdateTimeStamps region + + newTx = s.beginTransaction(); + + // please enable + // log4j.logger.org.hibernate.cache.StandardQueryCache=DEBUG + // log4j.logger.org.hibernate.cache.UpdateTimestampsCache=DEBUG + // to see that isUpToDate is called where not appropriated + + assertTrue( s.getSessionFactory().getStatistics().isStatisticsEnabled() ); + s.getSessionFactory().getStatistics().clear(); + + // should not produce a hit in StandardQuery cache region because there is a constraint + getCriteria( s ).add( Restrictions.isNull( "singleD" ) ).uniqueResult(); + + assertEquals( 0, s.getSessionFactory().getStatistics().getQueryCacheHitCount() ); + + s.createQuery( "delete from A" ).executeUpdate(); + newTx.commit(); + // Shutting down the application + s.close(); + } + + public void testCriteriaWithFetchModeJoinCollection() { + Session s = openSession(); + Transaction newTx = s.getTransaction(); + + newTx.begin(); + A a1 = new A(); + a1.setName( "name1" ); + D d1 = new D(); + a1.getDs().add( d1 ); + d1.setA( a1 ); + s.persist( d1 ); + s.persist( a1 ); + newTx.commit(); + + newTx = s.beginTransaction(); + getCriteria( s ).setFetchMode( "ds", FetchMode.JOIN ).uniqueResult(); // put query-result into cache + A a2 = new A(); + a2.setName( "xxxxxx" ); + s.persist( a2 ); + newTx.commit(); // Invalidates space A in UpdateTimeStamps region + + newTx = s.beginTransaction(); + + // please enable + // log4j.logger.org.hibernate.cache.StandardQueryCache=DEBUG + // log4j.logger.org.hibernate.cache.UpdateTimestampsCache=DEBUG + // to see that isUpToDate is called where not appropriated + + assertTrue( s.getSessionFactory().getStatistics().isStatisticsEnabled() ); + s.getSessionFactory().getStatistics().clear(); + + // should produce a hit in StandardQuery cache region + getCriteria( s ).setFetchMode( "ds", FetchMode.JOIN ).uniqueResult(); + + assertEquals( + "query is not considered as isImmutableNaturalKeyLookup, despite fullfilling all conditions", + 1, s.getSessionFactory().getStatistics().getQueryCacheHitCount() + ); + s.createQuery( "delete from D" ).executeUpdate(); + s.createQuery( "delete from A" ).executeUpdate(); + + newTx.commit(); + // Shutting down the application + s.close(); + } + + public void testCriteriaWithFetchModeJoinOnetoOne() { + Session s = openSession(); + Transaction newTx = s.getTransaction(); + + newTx.begin(); + A a1 = new A(); + a1.setName( "name1" ); + D d1 = new D(); + a1.setSingleD( d1 ); + d1.setSingleA( a1 ); + s.persist( d1 ); + s.persist( a1 ); + newTx.commit(); + + newTx = s.beginTransaction(); + getCriteria( s ).setFetchMode( "singleD", FetchMode.JOIN ).uniqueResult(); // put query-result into cache + A a2 = new A(); + a2.setName( "xxxxxx" ); + s.persist( a2 ); + newTx.commit(); // Invalidates space A in UpdateTimeStamps region + + newTx = s.beginTransaction(); + + // please enable + // log4j.logger.org.hibernate.cache.StandardQueryCache=DEBUG + // log4j.logger.org.hibernate.cache.UpdateTimestampsCache=DEBUG + // to see that isUpToDate is called where not appropriated + + assertTrue( s.getSessionFactory().getStatistics().isStatisticsEnabled() ); + s.getSessionFactory().getStatistics().clear(); + + // should produce a hit in StandardQuery cache region + getCriteria( s ).setFetchMode( "singleD", FetchMode.JOIN ).uniqueResult(); + + assertEquals( + "query is not considered as isImmutableNaturalKeyLookup, despite fullfilling all conditions", + 1, s.getSessionFactory().getStatistics().getQueryCacheHitCount() + ); + s.createQuery( "delete from A" ).executeUpdate(); + s.createQuery( "delete from D" ).executeUpdate(); + + newTx.commit(); + // Shutting down the application + s.close(); + } + + public void testCriteriaWithAliasOneToOneJoin() { + Session s = openSession(); + Transaction newTx = s.getTransaction(); + + newTx.begin(); + A a1 = new A(); + a1.setName( "name1" ); + D d1 = new D(); + a1.setSingleD( d1 ); + d1.setSingleA( a1 ); + s.persist( d1 ); + s.persist( a1 ); + newTx.commit(); + + newTx = s.beginTransaction(); + getCriteria( s ).createAlias( "singleD", "d", JoinFragment.LEFT_OUTER_JOIN ) + .uniqueResult(); // put query-result into cache + A a2 = new A(); + a2.setName( "xxxxxx" ); + s.persist( a2 ); + newTx.commit(); // Invalidates space A in UpdateTimeStamps region + + newTx = s.beginTransaction(); + + // please enable + // log4j.logger.org.hibernate.cache.StandardQueryCache=DEBUG + // log4j.logger.org.hibernate.cache.UpdateTimestampsCache=DEBUG + // to see that isUpToDate is called where not appropriated + + assertTrue( s.getSessionFactory().getStatistics().isStatisticsEnabled() ); + s.getSessionFactory().getStatistics().clear(); + + // should not produce a hit in StandardQuery cache region because createAlias() creates a subcriteria + getCriteria( s ).createAlias( "singleD", "d", JoinFragment.LEFT_OUTER_JOIN ).uniqueResult(); + + assertEquals( 0, s.getSessionFactory().getStatistics().getQueryCacheHitCount() ); + s.createQuery( "delete from A" ).executeUpdate(); + s.createQuery( "delete from D" ).executeUpdate(); + + newTx.commit(); + // Shutting down the application + s.close(); + } + + public void testSubCriteriaOneToOneJoin() { + Session s = openSession(); + Transaction newTx = s.getTransaction(); + + newTx.begin(); + A a1 = new A(); + a1.setName( "name1" ); + D d1 = new D(); + a1.setSingleD( d1 ); + d1.setSingleA( a1 ); + s.persist( d1 ); + s.persist( a1 ); + newTx.commit(); + + newTx = s.beginTransaction(); + getCriteria( s ).createCriteria( "singleD", "d", JoinFragment.LEFT_OUTER_JOIN ) + .uniqueResult(); // put query-result into cache + A a2 = new A(); + a2.setName( "xxxxxx" ); + s.persist( a2 ); + newTx.commit(); // Invalidates space A in UpdateTimeStamps region + + newTx = s.beginTransaction(); + + // please enable + // log4j.logger.org.hibernate.cache.StandardQueryCache=DEBUG + // log4j.logger.org.hibernate.cache.UpdateTimestampsCache=DEBUG + // to see that isUpToDate is called where not appropriated + + assertTrue( s.getSessionFactory().getStatistics().isStatisticsEnabled() ); + s.getSessionFactory().getStatistics().clear(); + + // should not produce a hit in StandardQuery cache region because createCriteria() creates a subcriteria + getCriteria( s ).createCriteria( "singleD", "d", JoinFragment.LEFT_OUTER_JOIN ).uniqueResult(); + + assertEquals( 0, s.getSessionFactory().getStatistics().getQueryCacheHitCount() ); + s.createQuery( "delete from A" ).executeUpdate(); + s.createQuery( "delete from D" ).executeUpdate(); + + newTx.commit(); + // Shutting down the application + s.close(); + } + + private Criteria getCriteria(Session s) { + Criteria crit = s.createCriteria( A.class, "anAlias" ); + crit.add( Restrictions.naturalId().set( "name", "name1" ) ); + crit.setFlushMode( FlushMode.COMMIT ); + crit.setCacheable( true ); + return crit; + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + A.class, + D.class + }; + } + + @Override + protected void configure(Configuration cfg) { + cfg.setProperty( Environment.GENERATE_STATISTICS, "true" ); + cfg.setProperty( Environment.USE_QUERY_CACHE, "true" ); + } + + +}