Cayenne has its own simple transaction API centered around org.apache.cayenne.access.Transaction class. Its goal is to ensure consistency of the DataContext database operations. It works either as a standalone mechanism, or in conjunction with another transaction framework, such as JTA or Spring. To switch between the two modes of operation, use "Container-Managed Transactions" checkbox in the DataDomain editing panel in CayenneModeler:
If this box is unchecked (default), standalone mode is used and Cayenne will take care of transactional resources management on its own. If it is checked, Cayenne won't commit or rollback transactional resources, relying on the external transaction manager to do that.
In both cases Transaction API works implicitly behind the scenes, so the application doesn't need to interact with it directly. In that Cayenne Transactions are fully declarative.
Similar to the Java EE approach, Cayenne transactions are bound to the current thread for the duration of the execution. For instance this is how Cayenne does an internal check of whether there is a transaction in progress:
When a Transaction is created inside Cayenne, it is immediately bound to the thread:
Now let's revisit the flow of a typical operation that requires a transaction:
- A DataContext sends a query or a commit request to the underlying org.apache.cayenne.DataChannel.
- The request travels the chain of DataChannels until it reaches one that is a org.apache.cayenne.access.DataDomain.
- DataDomain analyzes context request and dispatches data queries to one or more org.apache.cayenne.access.DataNodes.
- Each DataNode opens a JDBC Connection and executes queries.
Transactions come into play in step 3. DataDomain checks whether there is an existing Transaction in process and if not - creates and starts a new one (standalone or container, depending on the preconfigured type). In that Cayenne transaction policy is similar to Java EE "REQUIRE" policy.
Later in step 4 DataNodes will attach any of the Connections they obtains to the ongoing transaction:
If you want to execute some custom code, such as Cayenne queries or raw JDBC queries at certain points in transaction lifecycle, you need to implement a org.apache.cayenne.access.TransactionDelegate callback interface:
Then an instance can be registered with the DataDomain.
The delegate is shared by all DataContexts.
If the application needs to define its own transactional scope (e.g. wrap more than one DataContext.commitChanges() in a single database transaction), an explict org.apache.cayenne.access.Transaction can be started. It will serve as a simple substitute for the JTA transactions (of course JTA UserTransaction can be used instead if desired).
|If the user code starts a Transaction, it must explicitly invoke "commit/rollback" methods and unbind the Transaction from the current thread when it is finished. Failure to do that may result in connection leaks. Of course if Cayenne starts an implicit transaction, it does the cleanup internally on its own.|
Below is an example of user-controlled Transaction code. First it obtains a new transaction from the DataDomain (alternatively users can create Transaction subclasses of their own):
As we must finish transaction regardless of the outcome, wrap the rest of the code in try/catch/finally. Don't foget to bind/unbind the transaction, so that Cayenne stack is aware of it: