Starting with 5.0.0.Final, ModeShape has its own persistence mechanism offering a number of built-in providers. All these providers have some common characteristics which we'll outline below:
ModeShape 5, just like ModeShape 3 and ModesShape 4 uses and requires transactions.
There are two different flavor of transactions that ModeShape can work with:
- built-in transactions - these are completely transparent to the user and exert their effect each time a user performs a session.save() operation: all the changes within that session are persisted atomically as part of an internal transaction.
- user transactions - these are managed by someone other than the ModeShape internal code. In this case, some other code starts and commits or rolls-back a transaction, making one or multiple session.save calls in between. See this section for detailed information about JTA transactions and how to use them with ModeShape
In either case, ModeShape has to integrate with a JTA transaction manager in order to be able to start (1) or be aware (2) of existing transactions. To do this, you have to tell ModeShape what TransactionManager to use
Ideally in a production system, you should add and use an established JTA implementation (see below) but you also have the option of not using any explicit transaction management, in which case ModeShape will default to an in-memory implementation which is only suitable for testing or prototyping.
The typical way of telling ModeShape to use one transaction manager or another is by simply adding the appropriate implementation to your runtime classpath. ModeShape uses a transaction manager lookup class, which attempts at runtime to find one of the following JTA implementations in order:
- JNDI lookup one of:
- standalone implementation of JBoss JTA
- standalone implementation of Atomikos JTA
If any of the above are found, ModeShape will integrate with and use that JTA implementation. If none of the above are found, ModeShape will log a warning and default to a local, in-memory JTA implementation.
We're somewhat partial to the JBoss Transaction Manager. It's solid and used in the popular JBoss Application Server and Red Hat Middleware platforms. And it's what we use in our own testing and examples.
Using it is easy, especially if you're using our embedded BOM (as we describe in our Getting Started Guide), because all you have to do is add a dependency in your POM on the JBoss Transaction Manager:
Note that you don't need to (but can) specify the version, since our BOM already defines the default version.
|This is always used when running ModeShape inside JBoss AS via our JBoss AS kit|
If you're using the ModeShape BOM (as we describe in our Getting Started Guide), simply add the following dependency to your project:
If the default transaction manager lookup behavior is not suitable for your use case, you have the option of implementing your own org.modeshape.jcr.api.txn.TransactionManagerLookup and configuring ModeShape to use it like so:
where the org.modeshape.jcr.api.txn.TransactionManagerLookup API is straightforward:
All of ModeShape's persistence providers support multiple concurrent readers and writers, with effective READ_COMMITTED isolation. However, to ensure strict serializability, ModeShape uses global locking on each of the node keys which are modified as part of a transaction. This means that when multiple concurrent writers modify the same node, there will be lock contention proportionate to number of writer threads, which can significantly decrease performance.
For highly contented use cases, ModeShape offers the option of controlling the lock timeout interval before any writer thread will abort a transaction if it cannot obtain a lock on any of the modified nodes:
The default value is 10000, meaning 10 seconds.
Some persistence providers may have additional specific settings for controlling the number of concurrent threads making changes to the persistent store.
|Since ModeShape holds all global locks per transaction and thread, using JTA to change the owner thread of a transaction is not supported and will break the persistence code. This means that when using user-transactions you should never attempt to suspend and then resume a transaction off a different thread. See this issue for an example.|
ModeShape 3 and 4 used, in additional to the main Infinispan cache which stored the repository data, a second, local, in-memory cache, for each repository workspace in order to provider fast read access to frequently used nodes. This cache exists solely for performance reasons and ModeShape 5 preserves the concept, using a LRU ConcurrentMap implementation.
The number of entries held in this cache can be configured like so:
|The default value is 10000 but when clustering you can test using a higher number, especially if your system is read-intensive as this may provide better locality.|
Clustering in ModeShape 5 is much more conservative in order to ensure data integrity. As such, only persistence providers which are "naturally shareable" by multiple processes can be used when clustering. Currently the only provider supporting this is the database persistence provider.
See this section of our documentation for more information about clustering in ModeShape 5.
ModeShape 5 maintains the overall design of representing nodes as BSON documents, but instead of storing them in Infinispan it stores them in key-value transactional stores.
The SPI for this store is part of the modeshape-schematic module and has the following form:
By default, ModeShape 5 has the following list of persistence providers: