Skip to content

Commit 5201a45

Browse files
pb00068sebersole
authored andcommitted
HHH-14944 - Detaching an entity removes natural-id cross-reference from shared cache
1 parent c102b83 commit 5201a45

File tree

5 files changed

+102
-16
lines changed

5 files changed

+102
-16
lines changed

documentation/src/test/java/org/hibernate/userguide/caching/SecondLevelCacheTest.java

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,27 @@
2222
import org.hibernate.Session;
2323
import org.hibernate.annotations.CacheConcurrencyStrategy;
2424
import org.hibernate.annotations.NaturalId;
25+
import org.hibernate.annotations.NaturalIdCache;
2526
import org.hibernate.cfg.AvailableSettings;
27+
import org.hibernate.internal.SessionFactoryImpl;
2628
import org.hibernate.jpa.QueryHints;
2729
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
2830
import org.hibernate.stat.CacheRegionStatistics;
2931
import org.hibernate.stat.Statistics;
3032

33+
import org.hibernate.testing.TestForIssue;
3134
import org.junit.Ignore;
3235
import org.junit.Test;
3336

3437
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
38+
import static org.junit.Assert.assertEquals;
3539
import static org.junit.Assert.assertNotNull;
3640

3741

3842
/**
3943
* @author Vlad Mihalcea
4044
*/
41-
@Ignore
45+
4246
//@FailureExpected( jiraKey = "HHH-12146", message = "No idea why those changes cause this to fail, especially in the way it does" )
4347
public class SecondLevelCacheTest extends BaseEntityManagerFunctionalTestCase {
4448

@@ -251,10 +255,89 @@ public void testCache() {
251255
});
252256
}
253257

258+
@Test
259+
@TestForIssue( jiraKey = "HHH-14944") // issue is also reproduceable in Hibernate 5.4
260+
public void testCacheVerifyHits() {
261+
doInJPA( this::entityManagerFactory, entityManager -> {
262+
entityManager.persist( new Person() );
263+
Person aPerson= new Person();
264+
aPerson.setName( "John Doe" );
265+
aPerson.setCode( "unique-code" );
266+
entityManager.persist( aPerson );
267+
Session session = entityManager.unwrap(Session.class);
268+
SessionFactoryImpl sfi = (SessionFactoryImpl) session.getSessionFactory();
269+
sfi.getStatistics().clear();
270+
return aPerson;
271+
});
272+
273+
doInJPA(this::entityManagerFactory, entityManager -> {
274+
log.info("Native load by natural-id, generate first hit");
275+
276+
Session session = entityManager.unwrap(Session.class);
277+
SessionFactoryImpl sfi = (SessionFactoryImpl) session.getSessionFactory();
278+
//tag::caching-entity-natural-id-example[]
279+
Person person = session
280+
.byNaturalId(Person.class)
281+
.using("code", "unique-code")
282+
.load();
283+
284+
assertNotNull(person);
285+
log.info("NaturalIdCacheHitCount: " + sfi.getStatistics().getNaturalIdCacheHitCount());
286+
log.info("SecondLevelCacheHitCount: " + sfi.getStatistics().getSecondLevelCacheHitCount());
287+
assertEquals(1, sfi.getStatistics().getNaturalIdCacheHitCount());
288+
assertEquals(1, sfi.getStatistics().getSecondLevelCacheHitCount());
289+
//end::caching-entity-natural-id-example[]
290+
});
291+
292+
doInJPA(this::entityManagerFactory, entityManager -> {
293+
log.info("Native load by natural-id, generate second hit");
294+
295+
Session session = entityManager.unwrap(Session.class);
296+
SessionFactoryImpl sfi = (SessionFactoryImpl) session.getSessionFactory();
297+
//tag::caching-entity-natural-id-example[]
298+
Person person = session.bySimpleNaturalId(Person.class).load("unique-code");
299+
assertNotNull(person);
300+
301+
// resolve in persistence context (first level cache)
302+
session.bySimpleNaturalId(Person.class).load("unique-code");
303+
log.info("NaturalIdCacheHitCount: " + sfi.getStatistics().getNaturalIdCacheHitCount());
304+
log.info("SecondLevelCacheHitCount: " + sfi.getStatistics().getSecondLevelCacheHitCount());
305+
assertEquals(2, sfi.getStatistics().getNaturalIdCacheHitCount());
306+
assertEquals(2, sfi.getStatistics().getSecondLevelCacheHitCount());
307+
308+
session.clear();
309+
// persistence context (first level cache) empty, should resolve from second level cache
310+
log.info("Native load by natural-id, generate third hit");
311+
person = session.bySimpleNaturalId(Person.class).load("unique-code");
312+
log.info("NaturalIdCacheHitCount: " + sfi.getStatistics().getNaturalIdCacheHitCount());
313+
log.info("SecondLevelCacheHitCount: " + sfi.getStatistics().getSecondLevelCacheHitCount());
314+
assertNotNull(person);
315+
assertEquals(3, sfi.getStatistics().getNaturalIdCacheHitCount());
316+
assertEquals(3, sfi.getStatistics().getSecondLevelCacheHitCount());
317+
318+
//Remove the entity from the persistence context
319+
Long id = person.getId();
320+
321+
entityManager.detach(person); // still it should resolve from second level cache after this
322+
323+
log.info("Native load by natural-id, generate 4. hit");
324+
person = session.bySimpleNaturalId(Person.class).load("unique-code");
325+
log.info("NaturalIdCacheHitCount: " + sfi.getStatistics().getNaturalIdCacheHitCount());
326+
assertEquals("we expected now 4 hits" , 4, sfi.getStatistics().getNaturalIdCacheHitCount());
327+
assertNotNull(person);
328+
session.delete(person); // evicts natural-id from first & second level cache
329+
person = session.bySimpleNaturalId(Person.class).load("unique-code");
330+
assertEquals(4, sfi.getStatistics().getNaturalIdCacheHitCount()); // thus hits should not increment
331+
332+
//end::caching-entity-natural-id-example[]
333+
});
334+
}
335+
254336
//tag::caching-entity-natural-id-mapping-example[]
255337
@Entity(name = "Person")
256338
@Cacheable
257339
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
340+
@NaturalIdCache
258341
public static class Person {
259342

260343
@Id

hibernate-core/src/main/java/org/hibernate/engine/internal/NaturalIdXrefDelegate.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,14 @@ public boolean cacheNaturalIdCrossReference(EntityPersister persister, Serializa
8787
/**
8888
* Handle removing cross reference entries for the given natural-id/pk combo
8989
*
90-
* @param persister The persister representing the entity type.
91-
* @param pk The primary key value
90+
* @param persister The persister representing the entity type.
91+
* @param pk The primary key value
9292
* @param naturalIdValues The natural id value(s)
93-
*
93+
* @param removeOnNaturalIdCache remove the entry on shared cache too
94+
*
9495
* @return The cached values, if any. May be different from incoming values.
9596
*/
96-
public Object[] removeNaturalIdCrossReference(EntityPersister persister, Serializable pk, Object[] naturalIdValues) {
97+
public Object[] removeNaturalIdCrossReference(EntityPersister persister, Serializable pk, Object[] naturalIdValues, boolean removeOnNaturalIdCache) {
9798
persister = locatePersisterForKey( persister );
9899
validateNaturalId( persister, naturalIdValues );
99100

@@ -108,7 +109,7 @@ public Object[] removeNaturalIdCrossReference(EntityPersister persister, Seriali
108109
}
109110
}
110111

111-
if ( persister.hasNaturalIdCache() ) {
112+
if ( removeOnNaturalIdCache && persister.hasNaturalIdCache() ) {
112113
final NaturalIdDataAccess naturalIdCacheAccessStrategy = persister
113114
.getNaturalIdCacheAccessStrategy();
114115
final Object naturalIdCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( naturalIdValues, persister, session() );

hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2117,8 +2117,8 @@ public Object[] removeLocalNaturalIdCrossReference(EntityPersister persister, Se
21172117
final Object[] localNaturalIdValues = getNaturalIdXrefDelegate().removeNaturalIdCrossReference(
21182118
persister,
21192119
id,
2120-
naturalIdValues
2121-
);
2120+
naturalIdValues,
2121+
true);
21222122

21232123
return localNaturalIdValues != null ? localNaturalIdValues : naturalIdValues;
21242124
}
@@ -2235,11 +2235,12 @@ public void cleanupFromSynchronizations() {
22352235
}
22362236

22372237
@Override
2238-
public void handleEviction(Object object, EntityPersister persister, Serializable identifier) {
2238+
public void handleEviction(Object object, EntityPersister persister, Serializable identifier, boolean evictOnNaturalIdCache) {
22392239
getNaturalIdXrefDelegate().removeNaturalIdCrossReference(
22402240
persister,
22412241
identifier,
2242-
findCachedNaturalId( persister, identifier )
2242+
findCachedNaturalId( persister, identifier ),
2243+
evictOnNaturalIdCache
22432244
);
22442245
}
22452246
};

hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -950,11 +950,12 @@ void manageSharedNaturalIdCrossReference(
950950
/**
951951
* Called on {@link org.hibernate.Session#evict} to give a chance to clean up natural-id cross refs.
952952
*
953-
* @param object The entity instance.
954-
* @param persister The entity persister
955-
* @param identifier The entity identifier
953+
* @param object The entity instance.
954+
* @param persister The entity persister
955+
* @param identifier The entity identifier
956+
* @param removeOnNaturalIdCache remove the entry on shared cache too
956957
*/
957-
void handleEviction(Object object, EntityPersister persister, Serializable identifier);
958+
void handleEviction(Object object, EntityPersister persister, Serializable identifier, boolean removeOnNaturalIdCache);
958959
}
959960

960961
/**

hibernate-core/src/main/java/org/hibernate/event/internal/DefaultEvictEventListener.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ protected void doEvict(
113113
persistenceContext.getNaturalIdHelper().handleEviction(
114114
object,
115115
persister,
116-
key.getIdentifier()
117-
);
116+
key.getIdentifier(),
117+
false);
118118
}
119119

120120
// remove all collections for the entity from the session-level cache

0 commit comments

Comments
 (0)