blob: 5b387b7630708fc90c7e5a54d7de3cf4f91fbfd3 [file] [log] [blame]
Title: Understanding Transactions
<P>Cayenne has its own simple transaction API centered around <TT>org.apache.cayenne.access.Transaction</TT> 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 &quot;Container-Managed Transactions&quot; checkbox in the DataDomain editing panel in CayenneModeler:</P>
<P><SPAN class="image-wrap" style=""><IMG src="understanding-transactions.data/transactions-types.png" style="border: 0px solid black"></SPAN></P>
<P>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.</P>
<P>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.</P>
<H3><A name="UnderstandingTransactions-HowTransactionsWork"></A>How Transactions Work</H3>
<P>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:</P>
<DIV class="code panel" style="border-width: 1px;"><DIV class="codeContent panelContent">
<PRE class="code-java"><SPAN class="code-keyword">import</SPAN> org.apache.cayenne.access.Transaction;
...
Transaction currentTx = Transaction.getThreadTransaction();
<SPAN class="code-keyword">if</SPAN>(currentTx != <SPAN class="code-keyword">null</SPAN>) {
<SPAN class="code-comment">// transaction in process...
</SPAN>}
</PRE>
</DIV></DIV>
<P>When a Transaction is created inside Cayenne, it is immediately bound to the thread:</P>
<DIV class="code panel" style="border-width: 1px;"><DIV class="codeContent panelContent">
<PRE class="code-java">Transaction tx = ...;
Transaction.bindThreadTransaction(tx);</PRE>
</DIV></DIV>
<P>Now let's revisit the flow of a typical operation that requires a transaction:</P>
<OL>
<LI>A DataContext sends a query or a commit request to the underlying <TT>org.apache.cayenne.DataChannel</TT>.</LI>
<LI>The request travels the chain of DataChannels until it reaches one that is a <TT>org.apache.cayenne.access.DataDomain</TT>.</LI>
<LI>DataDomain analyzes context request and dispatches data queries to one or more <TT>org.apache.cayenne.access.DataNodes</TT>.</LI>
<LI>Each DataNode opens a JDBC Connection and executes queries.</LI>
</OL>
<P>Transactions come into play in <B>step 3</B>. 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 <TT>&quot;REQUIRE&quot;</TT> policy.</P>
<P>Later in <B>step 4</B> DataNodes will attach any of the Connections they obtains to the ongoing transaction:</P>
<DIV class="code panel" style="border-width: 1px;"><DIV class="codeContent panelContent">
<PRE class="code-java"><SPAN class="code-keyword">import</SPAN> java.sql.Connection;
...
Connection connection = ...
currentTx.addConnection(<SPAN class="code-quote">&quot;someKey&quot;</SPAN>, connection);</PRE>
</DIV></DIV>
<H3><A name="UnderstandingTransactions-TransactionLifecycleCallbacks%3ATransactionDelegate"></A>Transaction Lifecycle Callbacks: TransactionDelegate</H3>
<P>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 <TT>org.apache.cayenne.access.TransactionDelegate</TT> callback interface:</P>
<DIV class="code panel" style="border-width: 1px;"><DIV class="codeContent panelContent">
<PRE class="code-java">ublic class MyTxCallback <SPAN class="code-keyword">implements</SPAN> TransactionDelegate {
<SPAN class="code-keyword">public</SPAN> <SPAN class="code-object">boolean</SPAN> willCommit(Transaction transaction) {
<SPAN class="code-comment">// run extra query before transaction is committed
</SPAN>
<SPAN class="code-comment">// The results of it will be committed or rolled back together with the current Transaction.
</SPAN>
DataContext context = DataContext.getThreadDataContext();
context.performGenericQuery(<SPAN class="code-keyword">new</SPAN> SQLTemplate(X.class, <SPAN class="code-quote">&quot;...&quot;</SPAN>));
<SPAN class="code-comment">// <SPAN class="code-keyword">return</SPAN> <SPAN class="code-keyword">true</SPAN>, letting Cayenne know it should <SPAN class="code-keyword">continue</SPAN> with commit
</SPAN> <SPAN class="code-keyword">return</SPAN> <SPAN class="code-keyword">true</SPAN>;
}
<SPAN class="code-keyword">public</SPAN> <SPAN class="code-object">boolean</SPAN> willMarkAsRollbackOnly(Transaction transaction) {
<SPAN class="code-keyword">return</SPAN> <SPAN class="code-keyword">true</SPAN>;
}
<SPAN class="code-keyword">public</SPAN> <SPAN class="code-object">boolean</SPAN> willRollback(Transaction transaction) {
<SPAN class="code-keyword">return</SPAN> <SPAN class="code-keyword">true</SPAN>;
}
<SPAN class="code-keyword">public</SPAN> void didCommit(Transaction transaction) {
}
<SPAN class="code-keyword">public</SPAN> void didRollback(Transaction transaction) {
}
<SPAN class="code-keyword">public</SPAN> <SPAN class="code-object">boolean</SPAN> willAddConnection(Transaction transaction, Connection connection) {
<SPAN class="code-keyword">return</SPAN> <SPAN class="code-keyword">true</SPAN>;
}
}
</PRE>
</DIV></DIV>
<P>Then an instance can be registered with the DataDomain. </P>
<DIV class="code panel" style="border-width: 1px;"><DIV class="codeContent panelContent">
<PRE class="code-java">DataDomain domain = Configuration.getSharedConfiguration().getDomain();
domain.setTransactionDelegate(<SPAN class="code-keyword">new</SPAN> MyTxCallback());
</PRE>
</DIV></DIV>
<P>The delegate is shared by all DataContexts.</P>
<H3><A name="UnderstandingTransactions-UserDefinedTransactionScope"></A>User-Defined Transaction Scope</H3>
<P>If the application needs to define its own transactional scope (e.g. wrap more than one <TT>DataContext.commitChanges()</TT> in a single database transaction), an explict <TT>org.apache.cayenne.access.Transaction</TT> can be started. It will serve as a simple substitute for the JTA transactions (of course JTA UserTransaction can be used instead if desired).</P>
<DIV class="panelMacro"><TABLE class="noteMacro"><COLGROUP><COL width="24"><COL></COLGROUP><TR><TD valign="top"><IMG src="http://cayenne.apache.org/docs/1.2/images/icons/emoticons/warning.gif" width="16" height="16" align="absmiddle" alt="" border="0"></TD><TD>If the user code starts a Transaction, it <B>must</B> explicitly invoke &quot;commit/rollback&quot; 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.</TD></TR></TABLE></DIV>
<P>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):</P>
<DIV class="code panel" style="border-width: 1px;"><DIV class="codeContent panelContent">
<PRE class="code-java">DataDomain domain = Configuration.getSharedConfiguration().getDomain();
Transaction tx = domain.createTransaction();
</PRE>
</DIV></DIV>
<P>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:</P>
<DIV class="code panel" style="border-width: 1px;"><DIV class="codeContent panelContent">
<PRE class="code-java">Transaction.bindThreadTransaction(tx);
<SPAN class="code-keyword">try</SPAN> {
<SPAN class="code-comment">// <SPAN class="code-keyword">do</SPAN> something...
</SPAN> ....
<SPAN class="code-comment">// <SPAN class="code-keyword">if</SPAN> no failures, commit
</SPAN> tx.commit();
}
<SPAN class="code-keyword">catch</SPAN> (Exception ex) {
tx.setRollbackOnly();
}
<SPAN class="code-keyword">finally</SPAN> {
Transaction.bindThreadTransaction(<SPAN class="code-keyword">null</SPAN>);
<SPAN class="code-keyword">if</SPAN> (tx.getStatus() == Transaction.STATUS_MARKED_ROLLEDBACK) {
<SPAN class="code-keyword">try</SPAN> {
tx.rollback();
}
<SPAN class="code-keyword">catch</SPAN> (Exception rollbackEx) {
}
}
}
</PRE>
</DIV></DIV>