diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 693ce590042d..e02dd06fc47d 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -988,10 +988,13 @@ public Object initializeLazyProperty(String fieldName, Object entity, SessionImp if ( ce != null ) { final CacheEntry cacheEntry = (CacheEntry) getCacheEntryStructure().destructure( ce, factory ); final Object initializedValue = initializeLazyPropertiesFromCache( fieldName, entity, session, entry, cacheEntry ); - interceptor.attributeInitialized( fieldName ); + if (initializedValue != LazyPropertyInitializer.UNFETCHED_PROPERTY) { + // The following should be redundant, since the setter should have set this already. + // interceptor.attributeInitialized(fieldName); - // NOTE EARLY EXIT!!! - return initializedValue; + // NOTE EARLY EXIT!!! + return initializedValue; + } } } @@ -1146,13 +1149,24 @@ private Object initializeLazyPropertiesFromCache( Serializable[] disassembledValues = cacheEntry.getDisassembledState(); final Object[] snapshot = entry.getLoadedState(); for ( int j = 0; j < lazyPropertyNames.length; j++ ) { - final Object propValue = lazyPropertyTypes[j].assemble( - disassembledValues[lazyPropertyNumbers[j]], - session, - entity - ); - if ( initializeLazyProperty( fieldName, entity, session, snapshot, j, propValue ) ) { - result = propValue; + final Serializable cachedValue = disassembledValues[lazyPropertyNumbers[j]]; + final Type lazyPropertyType = lazyPropertyTypes[j]; + final String propertyName = lazyPropertyNames[j]; + if (cachedValue == LazyPropertyInitializer.UNFETCHED_PROPERTY) { + if (fieldName.equals(propertyName)) { + result = LazyPropertyInitializer.UNFETCHED_PROPERTY; + } + // don't try to initialize the unfetched property + } + else { + final Object propValue = lazyPropertyType.assemble( + cachedValue, + session, + entity + ); + if ( initializeLazyProperty( fieldName, entity, session, snapshot, j, propValue ) ) { + result = propValue; + } } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTest.java index 8e0a1fbd25d6..83c201707920 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTest.java @@ -9,6 +9,8 @@ import javassist.CtClass; import org.hibernate.test.bytecode.enhancement.lazy.cache.LazyInCacheTestTask; +import org.hibernate.test.bytecode.enhancement.lazy.cache.UninitializedAssociationsInCacheTask; +import org.hibernate.test.bytecode.enhancement.lazy.cache.UninitializedLazyBasicCacheTask; import org.hibernate.test.bytecode.enhancement.lazy.group.LazyGroupMappedByTestTask; import org.hibernate.test.bytecode.enhancement.lazy.group.LazyGroupUpdateTestTask; import org.hibernate.test.bytecode.enhancement.lazy.group.SimpleLazyGroupUpdateTestTask; @@ -157,6 +159,20 @@ public void testLazyCache() { EnhancerTestUtils.runEnhancerTestTask( LazyInCacheTestTask.class ); } + @Test + @TestForIssue( jiraKey = "HHH-11766") + public void testUninitializedAssociationsInCache() { + EnhancerTestUtils.runEnhancerTestTask( UninitializedAssociationsInCacheTask.class ); + } + + + @Test + @TestForIssue( jiraKey = "HHH-11766") + public void testUninitializedLazyBasicCache() { + EnhancerTestUtils.runEnhancerTestTask( UninitializedLazyBasicCacheTask.class ); + } + + @Test @TestForIssue( jiraKey = "HHH-10252" ) public void testCascadeDelete() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/cache/UninitializedAssociationsInCacheTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/cache/UninitializedAssociationsInCacheTask.java new file mode 100644 index 000000000000..104c767adbda --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/cache/UninitializedAssociationsInCacheTask.java @@ -0,0 +1,171 @@ +package org.hibernate.test.bytecode.enhancement.lazy.cache; + +import javax.persistence.Basic; +import javax.persistence.Cacheable; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +import org.hibernate.Hibernate; +import org.hibernate.Session; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.stat.SecondLevelCacheStatistics; + +import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; +import org.junit.Assert; + +import static org.junit.Assert.assertEquals; + +public class UninitializedAssociationsInCacheTask extends AbstractEnhancerTestTask { + + @Override + public Class[] getAnnotatedClasses() { + return new Class[]{Employee.class}; + } + + public void prepare() { + Configuration cfg = new Configuration(); + cfg.setProperty( AvailableSettings.USE_SECOND_LEVEL_CACHE, "true" ); + cfg.setProperty( AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS, "false" ); + cfg.setProperty( AvailableSettings.GENERATE_STATISTICS, "true" ); + prepare( cfg ); + } + + + public void execute() { + final AtomicLong bossId = new AtomicLong(); + final AtomicLong teamleaderId = new AtomicLong(); + final AtomicLong teammemberId = new AtomicLong(); + + Session s = getFactory().openSession(); + s.getTransaction().begin(); + { + Employee boss = new Employee(); + Employee teamleader = new Employee(); + Employee teammember = new Employee(); + boss.regularString = "boss"; + teamleader.regularString = "leader"; + teammember.regularString = "member"; + s.persist( boss ); + s.persist( teamleader ); + s.persist( teammember ); + boss.subordinates.add( teamleader ); + teamleader.superior = boss; + teamleader.subordinates.add( teammember ); + teammember.superior = teamleader; + bossId.set( boss.id ); + teamleaderId.set( teamleader.id ); + teammemberId.set( teammember.id ); + } + s.getTransaction().commit(); + s.close(); + + getFactory().getCache().evictAllRegions(); + getFactory().getStatistics().clear(); + SecondLevelCacheStatistics regionStatistics = getFactory().getStatistics().getSecondLevelCacheStatistics( + "hibernate.test.Employee" + ); + + s = getFactory().openSession(); + s.getTransaction().begin(); + { + final Employee boss = s.get( Employee.class, bossId.get() ); + Assert.assertEquals( "boss", boss.regularString ); + final Employee leader = s.get( Employee.class, teamleaderId.get() ); + Assert.assertEquals( "leader", leader.regularString ); + final Employee member = s.get( Employee.class, teammemberId.get() ); + Assert.assertEquals( "member", member.regularString ); + Assert.assertFalse( Hibernate.isPropertyInitialized( boss, "superior" ) ); + Assert.assertFalse( Hibernate.isPropertyInitialized( boss, "subordinates" ) ); + Assert.assertFalse( Hibernate.isPropertyInitialized( leader, "superior" ) ); + Assert.assertFalse( Hibernate.isPropertyInitialized( leader, "subordinates" ) ); + Assert.assertFalse( Hibernate.isPropertyInitialized( member, "superior" ) ); + Assert.assertFalse( Hibernate.isPropertyInitialized( member, "subordinates" ) ); + } + s.getTransaction().commit(); + s.close(); + + assertEquals( 0, regionStatistics.getHitCount() ); + assertEquals( 3, regionStatistics.getMissCount() ); + assertEquals( 3, regionStatistics.getPutCount() ); + + s = getFactory().openSession(); + s.getTransaction().begin(); + { + final Employee boss = s.get( Employee.class, bossId.get() ); + final Employee leader = s.get( Employee.class, teamleaderId.get() ); + final Employee member = s.get( Employee.class, teammemberId.get() ); + Assert.assertFalse( Hibernate.isPropertyInitialized( boss, "superior" ) ); + Assert.assertFalse( Hibernate.isPropertyInitialized( boss, "subordinates" ) ); + + Assert.assertFalse( Hibernate.isPropertyInitialized( member, "superior" ) ); + Assert.assertFalse( Hibernate.isPropertyInitialized( member, "subordinates" ) ); + Assert.assertNull( boss.superior ); + Assert.assertTrue( Hibernate.isPropertyInitialized( boss, "superior" ) ); + Assert.assertFalse( Hibernate.isPropertyInitialized( boss, "subordinates" ) ); + Assert.assertEquals( leader, boss.subordinates.iterator().next() ); + Assert.assertTrue( Hibernate.isPropertyInitialized( boss, "subordinates" ) ); + + Assert.assertFalse( Hibernate.isPropertyInitialized( leader, "superior" ) ); + Assert.assertFalse( Hibernate.isPropertyInitialized( leader, "subordinates" ) ); + Assert.assertEquals( boss, leader.superior ); + Assert.assertTrue( Hibernate.isPropertyInitialized( leader, "superior" ) ); + Assert.assertFalse( Hibernate.isPropertyInitialized( leader, "subordinates" ) ); + Assert.assertEquals( member, leader.subordinates.iterator().next() ); + Assert.assertTrue( Hibernate.isPropertyInitialized( leader, "subordinates" ) ); + + Assert.assertFalse( Hibernate.isPropertyInitialized( member, "superior" ) ); + Assert.assertFalse( Hibernate.isPropertyInitialized( member, "subordinates" ) ); + Assert.assertEquals( leader, member.superior ); + Assert.assertTrue( Hibernate.isPropertyInitialized( member, "superior" ) ); + Assert.assertFalse( Hibernate.isPropertyInitialized( member, "subordinates" ) ); + Assert.assertTrue( member.subordinates.isEmpty() ); + Assert.assertTrue( Hibernate.isPropertyInitialized( member, "subordinates" ) ); + } + s.getTransaction().commit(); + s.close(); + + assertEquals( 3, regionStatistics.getHitCount() ); + assertEquals( 3, regionStatistics.getMissCount() ); + assertEquals( 3, regionStatistics.getPutCount() ); + } + + protected void cleanup() { + } + + @Entity + @Table(name = "EMPLOYEE_TABLE") + @Cacheable + @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, region = "Employee") + private static class Employee { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "SUPERIOR") + @LazyToOne( value = LazyToOneOption.NO_PROXY ) + Employee superior; + + @OneToMany(mappedBy = "superior") + List subordinates = new ArrayList<>(); + + @Basic + String regularString; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/cache/UninitializedLazyBasicCacheTask.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/cache/UninitializedLazyBasicCacheTask.java new file mode 100644 index 000000000000..86dcfd6bf4b1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/cache/UninitializedLazyBasicCacheTask.java @@ -0,0 +1,131 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.bytecode.enhancement.lazy.cache; + +import javax.persistence.Basic; +import javax.persistence.Cacheable; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.Hibernate; +import org.hibernate.Session; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.stat.SecondLevelCacheStatistics; + +import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Aaron Schmischke + * @author Gail Badner + */ +public class UninitializedLazyBasicCacheTask extends AbstractEnhancerTestTask { + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { Person.class }; + } + + public void prepare() { + Configuration cfg = new Configuration(); + cfg.setProperty( AvailableSettings.ENABLE_LAZY_LOAD_NO_TRANS, "false" ); + cfg.setProperty( AvailableSettings.USE_SECOND_LEVEL_CACHE, "true" ); + cfg.setProperty( AvailableSettings.GENERATE_STATISTICS, "true" ); + prepare( cfg ); + } + + public void execute() { + + Session s = getFactory().openSession(); + s.getTransaction().begin(); + Person person = new Person(); + person.setLazyAttribute( "does_not_matter" ); + s.persist( person ); + s.getTransaction().commit(); + s.close(); + + final Long personId = person.getId(); + + getFactory().getStatistics().clear(); + getFactory().getCache().evictAllRegions(); + + s = getFactory().openSession(); + s.getTransaction().begin(); + { + person = s.get( Person.class, personId ); + assertFalse( Hibernate.isPropertyInitialized( person, "lazyAttribute" ) ); + } + s.getTransaction().commit(); + s.close(); + + SecondLevelCacheStatistics regionStatistics = getFactory().getStatistics().getSecondLevelCacheStatistics( + "hibernate.test.Person" + ); + assertEquals( 0, regionStatistics.getHitCount() ); + assertEquals( 1, regionStatistics.getMissCount() ); + assertEquals( 1, regionStatistics.getPutCount() ); + + s = getFactory().openSession(); + s.getTransaction().begin(); + { + person = s.get( Person.class, personId ); + assertFalse( Hibernate.isPropertyInitialized( person, "lazyAttribute" ) ); + person.getLazyAttribute(); + assertTrue( Hibernate.isPropertyInitialized( person, "lazyAttribute" ) ); + } + s.getTransaction().commit(); + s.close(); + + assertEquals( 1, regionStatistics.getHitCount() ); + assertEquals( 1, regionStatistics.getMissCount() ); + assertEquals( 1, regionStatistics.getPutCount() ); + } + + protected void cleanup() { + } + + @Cacheable + @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, include = "all", region = "Person") + @Entity(name = "Person") + public static class Person { + + @Id + @GeneratedValue + @Column(name = "id") + private Long id; + + @Column(name = "lazyAttribute") + @Basic(fetch = FetchType.LAZY) + private String lazyAttribute; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getLazyAttribute() { + return lazyAttribute; + } + + public void setLazyAttribute(String lazyAttribute) { + this.lazyAttribute = lazyAttribute; + } + + } +}