Table of contents
Open Table of contents
Article body
The problem:
While coding today, I found that a save operation ran successfully without any errors, but the corresponding database table had no new record. The console did not print the INSERT statement either. This was puzzling.
Why was the insert logic not being executed? I asked a colleague, and once they explained the reason, I felt rather foolish.
The cause:
We configure pointcuts in Spring’s configuration file so that the corresponding transactional operations run at those pointcuts. How do we usually configure these transactions? Typically, we specify them in XML, as shown here:
This specifies the method names that trigger transactions using tx:method. For example, at the pointcut I configured, all methods whose names begin with insert trigger a transaction. There are many other entries below. The configuration also specifies when to roll back: rollback-for=“Exception” means the transaction rolls back when an exception occurs. The propagation setting controls when to create a transaction, when to use the current transaction, and so on. You can look it up if it is unfamiliar. The last entry was the crucial one in my case.
The Spring transaction propagation settings most commonly used here are:
REQUIRED and SUPPORTS.
REQUIRED—if no transaction exists when the operation runs, create one.
SUPPORTS—if the current code is running within a transaction, participate in it; otherwise, run without a transaction.
The mistake in my code:
My method name did not match any of the names defined by tx:method in the XML. It was named cancel, so it did not trigger the corresponding transaction.
The XML configuration used SUPPORTS for all other method names. That explains why no transaction was started.
Here are the transaction propagation settings used in Spring configuration: (thanks to this blogger for sharing)
1: REQUIRED
If the operation is not already within another transaction, start a new one.
For example, suppose DemoServiceB.demoMethodB has REQUIRED propagation. When DemoServiceA.demoMethodA runs,
DemoServiceA.demoMethodA has already started a transaction. If it calls DemoServiceB.demoMethodB, that method sees that it is already running inside DemoServiceA.demoMethodA’s
transaction and does not start a new one. If DemoServiceA.demoMethodA finds that it is not already within a transaction, it starts one for itself.
An exception anywhere in DemoServiceA.demoMethodA or DemoServiceB.demoMethodB therefore rolls back the transaction. Even if DemoServiceB.demoMethodB’s transaction has been
committed, a subsequent failure and rollback in DemoServiceA.demoMethodA also rolls back DemoServiceB.demoMethodB.
2: SUPPORTS
Participate in the current transaction if one exists; otherwise, run without a transaction.
3: MANDATORY
The operation must run within a transaction. In other words, it can only be called within a parent transaction; otherwise, it throws an exception.
4: REQUIRES_NEW
This one takes a little more explanation. Suppose DemoServiceA.demoMethodA uses REQUIRED and DemoServiceB.demoMethodB uses REQUIRES_NEW.
When execution reaches DemoServiceB.demoMethodB, the transaction containing DemoServiceA.demoMethodA is suspended. DemoServiceB.demoMethodB starts a new transaction, and only after that transaction completes
does execution resume. The difference from REQUIRED is how rollback affects the operations. Since DemoServiceB.demoMethodB starts a new transaction, there are
two separate transactions. If DemoServiceB.demoMethodB has committed, a failure and rollback in DemoServiceA.demoMethodA does not roll it back. If DemoServiceB.demoMethodB fails and rolls back,
and DemoServiceA.demoMethodA catches the exception it throws, DemoServiceA.demoMethodA’s transaction may still commit.
5: NOT_SUPPORTED
Transactions are not supported for the operation. Suppose DemoServiceA.demoMethodA uses REQUIRED and DemoServiceB.demoMethodB uses NOT_SUPPORTED.
When execution reaches DemoServiceB.demoMethodB, DemoServiceA.demoMethodA’s transaction is suspended. DemoServiceB.demoMethodB runs without a transaction, after which DemoServiceA.demoMethodA’s transaction resumes.
6: NEVER
The operation cannot run within a transaction. If DemoServiceA.demoMethodA uses REQUIRED and DemoServiceB.demoMethodB uses NEVER,
DemoServiceB.demoMethodB throws an exception.
7: NESTED
The key to understanding NESTED is the savepoint. Unlike NESTED, REQUIRES_NEW starts a separate transaction that is independent of its parent.
A nested transaction depends on its parent and commits together with it. If the parent ultimately rolls back, the nested transaction rolls back too.