Are EhCache caches compatible with Spring's @Transactional annotation?

187 views
Skip to first unread message

Zac Harvey

unread,
Mar 18, 2022, 8:52:06 PM3/18/22
to ehcache-users
Spring Boot, JPA (Hibernate) and Java/JSR-107 Caching (EhCache) here.

I have a service method where I need to update both a table and a cache at the same time:
@Service
public class WorkService {

    @Autowired
    private PayloadRepository payloadRepository;

    @Autowired
    private Cache<String,Payload> payloadCache;
   
    // @Transactional ???
    public void doSomeWork(Payload payload) {

        // ... do some stuff up here

        payloadRepository.save(payload);

        // ... do some more stuff

        payloadCache.put(payload.getLabel(), payload);

        // ... do some finishing stuff down here

    }

}


I would like to make it so that both repository and cache lines above are treated _transactionally_; that is if there are any (uncaught? unexpected?) exceptions thrown from the doSomeWork(...) method, the payloadRepository.save(...) would rollback and the payloadCache.put(...) would also rollback. Meaning the new payload would _not_ exist either in the DB table or the cache.

I have perused the Spring Boot @Transactional docs, the Javax transactionality & XA docs, and the EhCache docs and I can't seem to determine whether I can just add @Transactional to this method and get the desired behavior or not.

If I can, I'd appreciate a quick explanation of _why_ the @Transactional anno would apply to the cache as well as the repository. If not, I guess I'm also hoping to get an explanation of why as well as what I would need to do to get this desired transactional behavior.

Again the desired state is: if anything uncaught/unexpected gets thrown from inside this method, if the repository save(...) method or the cache put(...) method have already been executed, I want them to rollback.

Thanks in advance!

Chris Dennis

unread,
Mar 19, 2022, 10:41:53 AM3/19/22
to ehcach...@googlegroups.com

The short answer here is yes, if everything is configured correctly then the cache will enlist in the transaction and you will see the guarantees you expect.

 

The main complication here is that due to the limitations (or scope, depending on your viewpoint) of the JTA spec there is a small amount of wiring necessary to connect the transaction manager to the cache. Ehcache 3.0 comes with wiring for Bitronix (in Java EE), and Narayana (in Jakarta EE) out of the box, anything else is left to the user to handle.

 

A very short explanation of why this works (although I’m far from a JTA/Spring/JDBC expert).

 

Precondition: Everyone is retrieving a reference to the same JTA TransactionManager, via whatever lookup mechanism is in place (often JNDI).

  1. When you enter a @Transactional method Spring retrieves the TM, and calls begin.
  2. When you interact with a JDBC database, a JMS queue or a Cache (or anything else appropriately configured/implemented) within that method: it retrieves the TM, asks for the current transaction context and enlists a resource in the transaction.
  3. When the leave the method depending on the config Spring will call either commit or rollback on the TM thus triggering the 2PC process on all the enlisted resources.

 

That’s basically it. The reason that extra wiring is needed is because the JTA spec is not sufficient to cover all (or really any interesting) recovery scenarios. This means each TM handles registration for recovery purposes differently, and so additional wiring must be done. Transaction managers typically handle this out-of-the-box for JDBC and other common things like that. Ehcache caches must go it alone, because they’re a little more unconventional.

 

Chris


Disclaimer: This message and files transmitted with it are confidential, intended solely for the use of the individual or entity to whom they are addressed. If you have received this in error, please notify us immediately and delete it with any attachments.


--
You received this message because you are subscribed to the Google Groups "ehcache-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ehcache-user...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/ehcache-users/bd508a16-9e78-4ac0-a1e7-f5e4ec8805f9n%40googlegroups.com.

Zac Harvey

unread,
Mar 23, 2022, 9:16:05 AM3/23/22
to ehcache-users
Thanks for the answer + explanation Chris. So it sounds like so long as I take care to wire my Spring Boot app (correctly) with, say, Bitronix, then the @Transactional annotation should "just work"?

Chris Dennis

unread,
Mar 23, 2022, 9:22:30 AM3/23/22
to ehcach...@googlegroups.com

If the cache is also configured as transactional then yes. If  the cache is transactional then it will complain if there is no started transaction when you go to interact with the cache for either write or read. If you get something wrong you’re likely to see the Cache complain about there being no transaction context. Basic documentation can be found here: https://www.ehcache.org/documentation/3.10/xa.html

 

Good Luck!

Zac Harvey

unread,
Mar 24, 2022, 5:01:01 PM3/24/22
to ehcache-users
Thanks again!

Last question on this (I promise!). Looking at the link you generously provided, it looks like the basic setup for creating an XA CacheManager is something like this:

BitronixTransactionManager transactionManager = TransactionManagerServices.getTransactionManager(); CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder() .using(new LookupTransactionManagerProviderConfiguration(BitronixTransactionManagerLookup.class)) .withCache("xaCache", CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class, ResourcePoolsBuilder.heap(10)) .withService(new XAStoreConfiguration("xaCache")) .build() ) .build(true); Cache<Long, String> xaCache = cacheManager.getCache("xaCache", Long.class, String.class);

The code examples then go on to show the transactionManager in use like so:

transactionManager.begin(); { xaCache.put(1L, "one"); } transactionManager.commit(); cacheManager.close(); transactionManager.shutdown();

So that's great and it shows me how to manage a transaction myself (manually). But where I'm having trouble seeing the forest through the trees is: I want the Spring @Transactional to do all that for me (begin, commit and shutdown).

So my hope is that all the code from transactionManager.begin() above through transactionManager.shutdown() is automatically handled for me by Spring when I annotate a method as being @Transactional and I use a properly configured EhCache cache inside that method. Is this the case? Or would I still need to begin, commit and shutdown transactions through the TM manually inside these @Transactional-annotated methods anyways?

Thanks again so much for all the help here!

Chris Dennis

unread,
Mar 24, 2022, 5:05:45 PM3/24/22
to ehcach...@googlegroups.com

Yes, that is the case. Spring will handle the begin, commit, and rollback (if it is necessary) and it should also handle the lifecycling of the transaction manager (the shutdown). The only thing you have to do is make sure that Spring and Ehcache are seeing the same TransactionManager.

Reply all
Reply to author
Forward
0 new messages