A repository may support transactions.
Whether a particular implementation supports transactions can be determined by querying the repository descriptor table with
Repository.OPTION_TRANSACTIONS_SUPPORTED.
A return value of true indicates support for transactions (see Repository Descriptors).
A repository that supports transactions must adhere to the Java Transaction API (JTA) specification23.
The actual methods used to control transaction boundaries are not defined by this specification. For example, there are no begin, commit or rollback methods in JCR API. These methods are defined by the JTA specification.
The JTA provides for two general approaches to transactions, container managed transactions and user managed transactions. In the first case, container managed transactions, the transaction management is taken care of by the application server and it is entirely transparent to the application using the API. The JTA interfaces javax.transaction.TransactionManager and javax.transaction.Transaction are the relevant ones in this context (though the client, as mentioned, will never have a need to use these).
In the second case, user managed transactions, the application using the API may choose to control transaction boundaries from within the application. In this case the relevant interface is javax.transaction.UserTransaction. This is the interface that provides the methods begin, commit, rollback and so forth. Note that behind the scenes the javax.transaction.TransactionManager and javax.transaction.Transaction are still employed, but again, the client does not deal with these.
A content repository implementation must support both of these approaches if it supports transactions.
Transactional
Application
Transaction
XARepository
XASession
XAResource
begin
getSession
login
new
new
getXAResource
enlistResource
start
application performs operations
logout
delistResource
end
commit
prepare
commit
Application
Server
Manager
// Get user transaction (for example, through JNDI)
UserTransaction utx = ...
// Get a node
Node n = ...
// Start a user transaction
utx.begin();
// Do some work
n.setProperty("myapp:title", "A Tale of Two Cities")
n.save();
// Do some more work
n.setProperty("myapp:author", "Charles Dickens")
n.save();
// Commit the user transaction
utx.commit();
Throughout this specification we often mention the distinction between transient and persistent levels. The persistent level refers to the (one or more) workspaces that make up the actual content storage of the repository. The transient level refers to in-memory storage associated with a particular Session object.
In these discussions we usually assume that operations occur outside the context of transactions; it is assumed that save and other workspace-altering methods immediately effect changes to the persistent layer, causing those changes to be made visible to other sessions.
This is not the case, however, once transactions are introduced. Within a transaction, changes made by save (or other, workspace-direct, methods) are transactionalized and are only persisted and published (made visible to other sessions), upon commit of the transaction. A rollback will, conversely, revert the effects of any saves or workspace-direct methods called within the transaction.
Note, however, that changes made in the transient storage are not recorded by a transaction. This means that a rollback will not revert changes made to the transient storage of the Session. After a rollback the Session object state will still contain any pending changes that were present before the rollback.
Because modifications in the transient layer are not transactionalized, the possibility exists for some implementations to allow a Session to be shared across transactions. This possibility arises because in JTA, an XAResource may be successively associated with different global transactions and in many implementations the natural mapping will be to make the Session implement the XAResource. The following code snippet illustrates how an XAResource may be shared across two global transactions:
//
Associate the resource (our Session) with a global
// transaction
xid1
res.start(xid1, TMNOFLAGS);
// Do something with res,
on behalf of xid1
// ...
//
Suspend work on this transaction
res.end(xid1, TMSUSPEND);
//
Associate (the same!) resource with another
// global transaction
xid2
res.start(xid2, TMNOFLAGS);
// Do something with res,
on behalf of xid2
// ...
// End work
res.end(xid2,
TMSUCCESS);
// Resume work with former
transaction
res.start(xid1, TMRESUME);
// Commit work
recorded when associated with xid2
res.commit(xid2, true);
In
cases where the XAResource
corresponds to a Session
(that is, probably most implementations), items that have been
obtained in the context of xid1
would still be valid when the Session
is effectively associated with xid2.
In other words, all transactions working on the same Session
would share the transient items obtained through that Session.
In those implementations that adopt a copy-on-read approach to transient storage (see ยง10.11.9 Seeing Changes Made by Other Sessions) this will mean that the a session is disassociated from a global transaction. This is however, outside the scope of this specification.