Terracotta 10.7 | Ehcache API Developer Guide | Transactions Support | Configuring it all in Java
 
Configuring it all in Java
The simplest case
The simplest possible configuration is to configure a cache manager as transactionally aware by using the provided Bitronix transaction manager integration.
This INFO level log entry informs you of the detected transaction manager:

INFO org.ehcache.transactions.xa.txmgr.btm.BitronixTransactionManagerLookup -
Using looked up transaction manager :
a BitronixTransactionManager with 0 in-flight transaction(s)
Here is an example:
BitronixTransactionManager transactionManager =
TransactionManagerServices.getTransactionManager(); // 1

CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.using(new LookupTransactionManagerProviderConfiguration(
BitronixTransactionManagerLookup.class)) // 2
.withCache("xaCache", CacheConfigurationBuilder.newCacheConfigurationBuilder(
Long.class, String.class, // 3
ResourcePoolsBuilder.heap(10)) // 4
.add(new XAStoreConfiguration("xaCache")) // 5
.build()
)
.build(true);

final Cache<Long, String> xaCache = cacheManager.getCache("xaCache", Long.class,
String.class);

transactionManager.begin(); // 6
{
xaCache.put(1L, "one"); // 7
}
transactionManager.commit(); // 8

cacheManager.close();
transactionManager.shutdown();
1
First start the Bitronix transaction manager. By default, Ehcache will auto-detect it but will throw an exception during the cache manager initialization if BTM isn't started.
2
Configure the cache manager such as it can handle transactions by having a TransactionManagerProvider loaded and configured to use Bitronix.
3
Register a cache the normal way.
4
Give it the resources you wish.
5
Add a XAStoreConfiguration object to make the cache XA transactional. You must also give the cache a unique XAResource identifier as some transaction managers require this.
6
Begin a JTA transaction the normal way.
7
Work with the cache the normal way, all operations are supported. Note that concurrent transactions will not see those pending changes.
8
Commit the JTA transaction. Other transactions can now see the changes you made to the cache.
Configuring your transaction manager
While only the Bitronix JTA implementation has been tested so far, plugging-in another one is possible.
You will need to implement a org.ehcache.transactions.xa.txmgr.provider.TransactionManagerLookup and make sure you understand its expected lifecycle as well as the one of the org.ehcache.transactions.xa.txmgr.provider.LookupTransactionManagerProvider.
If such a lifecycle does not match your needs, you will have to go one step further and implement your own org.ehcache.transactions.xa.txmgr.provider.TransactionManagerProvider.
XA write-through cache
When a XA cache is configured in write-though mode, the targeted SoR will automatically participate in the JTA transaction context. Nothing special needs to be configured for this to happen, just ensure that the configured CacheLoaderWriter is configured to work with XA transactions.
BitronixTransactionManager transactionManager =
TransactionManagerServices.getTransactionManager(); // 1

Class<CacheLoaderWriter<?, ?>> klazz =
(Class<CacheLoaderWriter<?, ?>>) (Class) (SampleLoaderWriter.class);

CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.using(new LookupTransactionManagerProviderConfiguration(
BitronixTransactionManagerLookup.class)) // 2
.withCache("xaCache", CacheConfigurationBuilder.newCacheConfigurationBuilder(
Long.class, String.class, // 3
ResourcePoolsBuilder.heap(10)) // 4
.add(new XAStoreConfiguration("xaCache")) // 5
.add(new DefaultCacheLoaderWriterConfiguration(klazz,
singletonMap(1L, "eins"))) // 6
.build()
)
.build(true);

final Cache<Long, String> xaCache = cacheManager.getCache("xaCache",
Long.class, String.class);

transactionManager.begin(); // 7
{
assertThat(xaCache.get(1L), equalTo("eins")); // 8
xaCache.put(1L, "one"); // 9
}
transactionManager.commit(); // 10

cacheManager.close();
transactionManager.shutdown();
1
First start the Bitronix transaction manager. By default, Ehcache will auto-detect it but will throw an exception during the cache manager initialization if BTM isn't started.
2
Configure the cache manager such as it can handle transactions by having a TransactionManagerProvider loaded and configured to use Bitronix.
3
Register a cache the normal way.
4
Give it the resources you wish.
5
Add a XAStoreConfiguration object to make the cache XA transactional. You must also give the cache a unique XAResource identifier as some transaction managers require this.
6
Add a CacheLoaderWriter configuration. This one is a mocked SoR backed by a map for illustration purpose that is filled with 1L/"eins" key/value pair at startup.
7
Begin a JTA transaction the normal way.
8
The cache is empty at startup, so the CacheLoaderWriter will be called to load the value.
9
Update the value. This will make the CacheLoaderWriter write to the SoR.
10
Commit the JTA transaction. Other transactions can now see the changes you made to the cache and the SoR.
Transactional scope
A XA cache can only be accessed within a JTA transaction's context. Any attempt to access one outside of such context will result in XACacheException to be thrown.
BitronixTransactionManager transactionManager =
TransactionManagerServices.getTransactionManager(); // 1

CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.using(new LookupTransactionManagerProviderConfiguration(
BitronixTransactionManagerLookup.class)) // 2
.withCache("xaCache", CacheConfigurationBuilder.newCacheConfigurationBuilder(
Long.class, String.class, // 3
ResourcePoolsBuilder.heap(10)) // 4
.add(new XAStoreConfiguration("xaCache")) // 5
.build()
)
.build(true);

final Cache<Long, String> xaCache = cacheManager.getCache("xaCache",
Long.class, String.class);

try {
xaCache.get(1L); // 6
fail("expected XACacheException");
} catch (XACacheException e) {
// expected
}

cacheManager.close();
transactionManager.shutdown();
1
First start the Bitronix transaction manager. By default, Ehcache will auto-detect it but will throw an exception during the cache manager initialization if BTM isn't started.
2
Configure the cache manager such as it can handle transactions by having a TransactionManagerProvider loaded and configured to use Bitronix.
3
Register a cache the normal way.
4
Give it the resources you wish.
5
Add a XAStoreConfiguration object to make the cache XA transactional. You must also give the cache a unique XAResource identifier as some transaction managers require this.
6
The cache is being accessed with no prior call to transactionManager.begin() which makes it throw XACacheException.
Note: there is one exception to that rule: the Cache.clear() method will always wipe the cache's contents non-transactionally.
XA cache with three tiers and persistence
When a cache is configured as persistent, the in-doubt transactions are preserved and can be recovered across restarts.
This INFO log informs you about that in-doubt transactions journaling is persistent too:
INFO o.e.t.x.j.DefaultJournalProvider - Using persistent XAStore journal
Here is an example:
BitronixTransactionManager transactionManager =
TransactionManagerServices.getTransactionManager(); // 1

PersistentCacheManager persistentCacheManager =
CacheManagerBuilder.newCacheManagerBuilder()
.using(new LookupTransactionManagerProviderConfiguration(
BitronixTransactionManagerLookup.class)) // 2
.with(new CacheManagerPersistenceConfiguration(new File(getStoragePath(),
"testXACacheWithThreeTiers"))) // 3
.withCache("xaCache", CacheConfigurationBuilder.newCacheConfigurationBuilder(
Long.class, String.class, // 4
ResourcePoolsBuilder.newResourcePoolsBuilder() // 5
.heap(10, EntryUnit.ENTRIES)
.offheap(10, MemoryUnit.MB)
.disk(20, MemoryUnit.MB, true)
)
.add(new XAStoreConfiguration("xaCache")) // 6
.build()
)
.build(true);

final Cache<Long, String> xaCache = persistentCacheManager.getCache("xaCache",
Long.class, String.class);

transactionManager.begin(); // 7
{
xaCache.put(1L, "one"); // 8
}
transactionManager.commit(); // 9

persistentCacheManager.close();
transactionManager.shutdown();
1
First start the Bitronix transaction manager. By default, Ehcache will auto-detect it but will throw an exception during the cache manager initialization if BTM isn't started.
2
Configure the cache manager such as it can handle transactions by having a TransactionManagerProvider loaded and configured to use Bitronix.
3
Configure persistence support to enable the use of the disk tier.
4
Register a cache the normal way.
5
Give it the resources you want.
6
Add a XAStoreConfiguration object to make the cache XA transactional. You must also give the cache a unique XAResource identifier as some transaction managers require this.
7
Begin a JTA transaction the normal way.
8
Update the value.
9
Commit the JTA transaction. Other transactions can now see the changes you made to the cache and the SoR.