We are trying to enable 2nd level cache in HBN application and we have faced with a couple of following issues:
#1 DefaultInitializeCollectionEventListener does not honour owner version when initializing collection from cache
I’m actually not sure the scenario below is valid from ORM perspective, however I do believe there is a room for improvement.
Say we have an entity class:
@NoArgsConstructor
@Setter
@Getter
public class TestEntity {
@Id
private Long id;
@Version
private Long stamp;
@Setter(AccessLevel.NONE)
@ElementCollection
@CollectionTable(name = "test_entity_rows"
@OrderColumn(name = "position")
private List<String> rows = new ArrayList<>();
}
and some entities of that class get cached, now, for some reason, I can’t really explain, we would like to modify entity via direct SQL updates, since all such entities are backed up by version stamp I do except that direct SQL updates which increase version stamp as well should be “safe”:
- application either sees previous version of entity and will fail to modify it
- application loads new consistent entity state from DB
Unfortunately, that does not work as expected: DefaultInitializeCollectionEventListener
fails to check “embedded collection version”, which is stored in AbstractReadWriteAccess.Item
, when it loads collection from cache, and as a result we are getting new owner with stale collection.
#2 CollectionLoadContext does not honour hibernate.enable_lazy_load_no_trans setting
The problem is when hibernate.enable_lazy_load_no_trans
is set to true
and CollectionLoadContext
tries to put collection into 2nd level cache it expects to find collection owner in temporary persistence context:
// !!! collection owner is always null when
// !!! hibernate.enable_lazy_load_no_trans=true
if ( collectionOwner == null ) {
throw new HibernateException(
"Unable to resolve owner of loading collection [" +
MessageHelper.collectionInfoString( persister, lce.getCollection(), lce.getKey(), session ) +
"] for second level caching"
);
}
#3 Detaching entities cause Hibernate to remove NaturalId mapping from cache
Here I’m not sure what is actually wrong and how that should work in case of detach:
- SessionImpl#detach calls SessionImpl#evict which in turn fires evict event
- DefaultEvictEventListener#doEvict calls PersistenceContext.NaturalIdHelper#handleEviction, as a result NaturalId mapping gets removed from 2nd-level cache, although our goal was just detach entity
- successive
new EvictVisitor( session, object ).process( object, persister );
call inDefaultEvictEventListener
actually does nothing
#4 AbstractSharedSessionContract returns wrong instance of CacheTransactionSynchronization
There is a special handling of TransactionCoordinator
in AbstractSharedSessionContract
for cases when application is doing something like:
try(Session nestedSession = existingSession.sessionWithOptions()
.connection()
.openSession()) {
// do some work within nestedSession
}
However, Hibernate does not perform similar handling for CacheTransactionSynchronization
, as a result, when nested session modifies data and “commits transaction” it’s own instance of CacheTransactionSynchronization
starts notifying global cache about changes (please check infinispan implementation for example) although data was not yet committed to DB.
#5 CacheEntityLoaderHelper fails to update NaturalId mapping in persistence context
The problem is when 2nd-level cache is enabled, entities have natural id and CacheEntityLoaderHelper#convertCacheEntryToEntity
method gets called, it injects entities into persistence context and fires post load event:
persistenceContext.addEntry(
entity,
( isReadOnly ? Status.READ_ONLY : Status.MANAGED ),
values,
null,
entityId,
version,
LockMode.NONE,
true,
subclassPersister,
false
);
subclassPersister.afterInitialize( entity, session );
persistenceContext.initializeNonLazyCollections();
//PostLoad is needed for EJB3
PostLoadEvent postLoadEvent = event.getPostLoadEvent()
.setEntity( entity )
.setId( entityId )
.setPersister( persister );
and, unfortunately, it fails to update/load Natural Id mapping (neither direct calls of PersistenceContext.NaturalIdHelper
nor firing load event), that would be just performance penalty if NaturalIdXrefDelegate
didn’t perform validation of Natural Id in NaturalIdXrefDelegate#validateNaturalId
, however in our case we are getting NullPointer exceptions in NaturalIdXrefDelegate#validateNaturalId
if entities were loaded from cache via MultiIdentifierLoadAccess