| Ehcache 3.1+ jar no longer contains the transaction related code. This is now available through a different binary:  | 
| Ehcache 3.1 added clustering support but this is not yet compatible with transactional caches. | 
What is supported and what are the limitations?
Ehcache supports caches that work within the context of an XA transaction controlled by a Java Transaction API (JTA) transaction manager. Within this context, Ehcache supports the two-phase commit protocol, including crash recovery.
- 
Bitronix Transaction Manager 2.1.4, which is an open source project hosted on GitHub, is the only tested transaction manager. Other transaction managers may work but have not yet been tested. 
- 
Read-Committed is the only supported isolation level. 
- 
The isolation level is guaranteed by the use of the Copiermechanism. When no copiers are configured for either the key or the value, default ones are automatically used instead. You cannot disable theCopiermechanism for a transactional cache.
- 
Accessing a cache outside of a JTA transaction context is forbidden. 
- 
There is no protection against the ABA problem. 
- 
Everything else works orthogonally. 
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);
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 TransactionManagerProviderloaded and configured to use Bitronix. | 
| 3 | Register a cache the normal way. | 
| 4 | Give it the resources you wish. | 
| 5 | Add a XAStoreConfigurationobject to make the cache XA transactional.
You must also give the cache a uniqueXAResourceidentifier 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);
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 TransactionManagerProviderloaded and configured to use Bitronix. | 
| 3 | Register a cache the normal way. | 
| 4 | Give it the resources you wish. | 
| 5 | Add a XAStoreConfigurationobject to make the cache XA transactional.
You must also give the cache a uniqueXAResourceidentifier as some transaction managers require this. | 
| 6 | Add a CacheLoaderWriterconfiguration.
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 CacheLoaderWriterwill be called to load the value. | 
| 9 | Update the value.
This will make the CacheLoaderWriterwrite 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);
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 3 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 TransactionManagerProviderloaded and configured to use Bitronix. | 
| 3 | Register a cache the normal way. | 
| 4 | Give it the resources you wish. | 
| 5 | Add a XAStoreConfigurationobject to make the cache XA transactional.
You must also give the cache a uniqueXAResourceidentifier as some transaction managers require this. | 
| 6 | The cache is being accessed with no prior call to transactionManager.begin()which makes it throwXACacheException. | 
| 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(CacheManagerBuilder.persistence(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);
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 3 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 TransactionManagerProviderloaded 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 XAStoreConfigurationobject to make the cache XA transactional.
You must also give the cache a uniqueXAResourceidentifier 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. | 
Configuring it with XML
You can create a XML file to configure a CacheManager, lookup a specific transaction manager and configure XA caches:
<service>
  <tx:jta-tm transaction-manager-lookup-class="org.ehcache.transactions.xa.txmgr.btm.BitronixTransactionManagerLookup"/> (1)
</service>
<cache alias="xaCache"> (2)
  <key-type>java.lang.String</key-type>
  <value-type>java.lang.String</value-type>
  <heap unit="entries">20</heap>
  <tx:xa-store unique-XAResource-id="xaCache" /> (3)
</cache>| 1 | Declare a TransactionManagerLookupthat will lookup your transaction manager. | 
| 2 | Configure a xaCachecache the normal way. | 
| 3 | Configure xaCacheas an XA cache, giving it xaCache as its uniqueXAResourceID. | 
In order to parse an XML configuration, you can use the XmlConfiguration type:
BitronixTransactionManager transactionManager =
    TransactionManagerServices.getTransactionManager(); (1)
URL myUrl = this.getClass().getResource("/docs/configs/xa-getting-started.xml"); (2)
Configuration xmlConfig = new XmlConfiguration(myUrl); (3)
CacheManager myCacheManager = CacheManagerBuilder.newCacheManager(xmlConfig); (4)
myCacheManager.init();
myCacheManager.close();
transactionManager.shutdown();| 1 | The Bitronix transaction manager must be started before the cache manager is initialized. | 
| 2 | Create a URL to your XML file’s location. | 
| 3 | Instantiate a XmlConfigurationpassing it the XML file’sURL. | 
| 4 | Using the static org.ehcache.config.builders.CacheManagerBuilder.newCacheManager(org.ehcache.config.Configuration)lets you create yourCacheManagerinstance using theConfigurationfrom theXmlConfiguration. | 
And here is what the BitronixTransactionManagerLookup implementation looks like:
public class BitronixTransactionManagerLookup implements TransactionManagerLookup { (1)
  private static final Logger LOGGER = LoggerFactory.getLogger(BitronixTransactionManagerLookup.class);
  @Override
  public TransactionManagerWrapper lookupTransactionManagerWrapper() { (2)
    if (!TransactionManagerServices.isTransactionManagerRunning()) { (3)
      throw new IllegalStateException("BTM must be started beforehand");
    }
    TransactionManagerWrapper tmWrapper = new TransactionManagerWrapper(TransactionManagerServices.getTransactionManager(),
        new BitronixXAResourceRegistry()); (4)
    LOGGER.info("Using looked up transaction manager : {}", tmWrapper);
    return tmWrapper;
  }
}| 1 | The TransactionManagerLookupinterface must be implemented and the offer ano-argconstructor. | 
| 2 | The lookupTransactionManagerWrapper()method must return aTransactionManagerWrapperinstance. | 
| 3 | Here is the check that makes sure BTM is started. | 
| 4 | The TransactionManagerWrapperclass is constructed with both thejavax.transaction.TransactionManagerinstance as well as aXAResourceRegistryinstance.
The latter is used to register thejavax.transaction.xa.XAResourceinstances of the cache with the transaction manager using an implementation-specific mechanism.
If your JTA implementation doesn’t require that, you can use theNullXAResourceRegistryinstead. |