hello everyone! please could you create a profile for me on jira so i can submit this issue there? thank you!
On Tue, Feb 28, 2023 at 10:42 PM Prigoreanu, Alexandru < [email protected]> wrote: > Hello everyone! thank you for your time. > > - we have an entity PlaceImpl annotated @Cache(usage = > CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) > - we use Ignite's L2 Hibernate cache implementation through the > application property > spring.jpa.properties.hibernate.cache.region.factory_class=org.apache.ignite.cache.hibernate.HibernateRegionFactory > - we use *ignite 2.14.0, ignite-hibernate-ext 5.3.0, hibernate 5.4.33* > - we have a complex transaction that creates a new PlaceImpl, saves it in > the database, and updates it. > - *when the PlaceImpl is returned from the level 2 cache it does not > contain the latest data for some fields. let's say we expect that > PlaceImpl.description is not null while PlaceImpl.description is null.* > > after a bit of debugging we got the following data: > - *during the transaction the changes to PlaceImpl are flushed twice or > more*: one in the middle of the transaction and the second one before the > transaction is committed. > - given the CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, this results in *2 > EntityUpdateAction for our PlaceImpl* that was created (we don't talk > about inserts here) added to the collection of processes in > AfterTransactionCompletionProcessQueue.processes > - after the transaction is completed, on the processes of the > aforementioned list hibernate invokes doAfterTransactionCompletion > > public void afterTransactionCompletion(boolean success) { >> while ( !processes.isEmpty() ) { >> try { >> processes.poll().doAfterTransactionCompletion( success, session ); >> } > > > - the first EntityUpdateAction contains an incomplete PlaceImpl that does > not yet have all the fields set > - the second EntityUpdateAction contains the complete PlaceImpl with all > the fields set > > - the first *EntityUpdateAction.doAfterTransactionCompletion gets to > execute HibernateNonStrictAccessStrategy.afterUpdate*: here *ctx is not > null* and the if ctx != null branch is executed and *the incomplete > PlaceImpl is put in the level 2 cache* > > @Override public boolean afterUpdate(Object key, Object val) { >> WriteContext ctx = writeCtx.get(); > > >> if (log.isDebugEnabled()) >> log.debug("Put after update [cache=" + cache.name() + ", key=" + >> key + ", val=" + val + ']'); > > >> if (ctx != null) { >> ctx.updated(key, val); > > >> unlock(key); > > >> return true; >> } > > >> return false; >> } > > > which invokes also unlock(key); > > @Override public void unlock(Object key) { >> try { >> WriteContext ctx = writeCtx.get(); > > >> if (ctx != null && ctx.unlocked(key)) { >> writeCtx.remove(); > > >> ctx.updateCache(cache); >> } >> } > > catch (IgniteCheckedException e) { >> throw convertException(e); >> } >> } > > > that *removes writeCtx from the current thread with writeCtx.remove();* > > - the second *EntityUpdateAction.doAfterTransactionCompletion gets to > execute HibernateNonStrictAccessStrategy.afterUpdate*: here *ctx is null* and > the if ctx != null branch is not executed, *so the level 2 cache is never > updated with the latest changes in the PlaceImpl entity*. > > we were able to have a minimal dummy text example of the transaction that > creates a PlaceImpl > > @Test >> public void testPlaceImplCacheWorksWithFlush() throws Exception { >> long[] placeId = new long [] {0L}; >> doInTransaction(() -> { >> PlaceImpl place = new PlaceImpl(); >> entityManager.persist(place); >> placeId[0] = place.getId(); >> entityManager.flush(); >> place.setName("NAME"); //set some place properties >> entityManager.flush(); >> place.setDescription("description"); //set some other place >> properties >> assertThat(place.getDescription(), Matchers.is("description")); >> }); >> //load place from the cache >> Place place = placeImplRepository.findOne(placeId[0]); >> assertThat(place.getName(), Matchers.is("NAME")); >> //the following assertion fails >> assertThat(place.getDescription(), Matchers.is("description")); >> } > > > we are aware the given example should not manually invoke flushes but in > our real transaction the flush is not manual, our code provokes > inadvertently autoFlushIfRequired that happens to flush also updates to our > new PlaceImpl entity > > what are your thoughts on the matter? > could this be a bug? > should we not use Ignite's hibernate level 2 cache implementation > HibernateRegionFactory when transactions update entities with multiple > flushes? > if you have any pointers on a solution we could also try to provide you a > pull request with the implementation. > > thank you for your time! > > Alex >
