Spring Transaction Management In REST API With Spring Boot
Transactional Methods
What happens when a method is transactional?
The method executes inside of a transaction, if anything goes wrong in the middle of the method execution, the transaction will be rolled back, otherwise, it will be committed.
Transactional Method Example
Let’s say there were 100 comments in a database, and while a method is being executed, 30 of them were successfully streamed, but then something happens in the middle of it and it could not read from the database anymore.
If the method runs within a transaction, the whole method will fail instead of returning 30 comments.
That prevents us from getting an incomplete list of comments.
How Spring Transactions Work | What Happens Behind The Scene?
Spring provides comprehensive transaction support, and the following is a very simplified overview of how it works.
Spring Framework’s transaction support is enabled via AOP proxies. So the caller of the method invokes the proxy, not the target, and at this point, a transaction is created. Then the target method is invoked, and on the way back, either the transaction is committed or rolled back.
How To Enable Spring’s Transaction Management
Typically transaction management is enabled using @EnableTransactionManagement
annotation or it could also be done via XML.
We are building a Spring Boot application, so most of the configuration is done for me. because I have spring-data libraries in the classpath, transaction management is enabled by the framework. So, you don’t have to do anything to enable transaction management.
In order to apply transaction management, all you have to do is add the @Transactional
annotation.
What Is @Transactional Annotation In Spring?
The @Transactional
annotation is metadata that specifies that an interface, class, or method must have transactional semantics. For example, “start a brand new read-only transaction when this method is invoked, suspending any existing transaction”.
There are quite a few settings that can be applied to this annotation. The following table from the documentation lists all of them.
@Transactional Settings
Property | Type | Description |
---|---|---|
value |
String | Optional qualifier specifying the transaction manager to be used. |
propagation |
enum: Propagation |
Optional propagation setting. |
isolation |
enum: Isolation |
Optional isolation level. |
readOnly |
boolean | Read/write vs. read-only transaction |
timeout |
int (in seconds granularity) | Transaction timeout. |
rollbackFor |
Array of Class objects, which must be derived from Throwable. | Optional array of exception classes that must cause rollback. |
rollbackForClassName |
Array of class names. Classes must be derived from Throwable. | Optional array of names of exception classes that must cause rollback. |
noRollbackFor |
Array of Class objects, which must be derived from Throwable. | Optional array of exception classes that must not cause rollback. |
noRollbackForClassName |
Array of String class names, which must be derived from Throwable. | Optional array of names of exception classes that must not cause rollback. |
Defaults Of Some @Transactional Settings
Property | Default value |
---|---|
propagation |
PROPAGATION_REQUIRED |
Isolation |
ISOLATION_DEFAULT |
readOnly |
read/write. |
timeout |
The default timeout of the underlying transaction system, or to none if timeouts are not supported. |
rollbackFor |
RuntimeExceptions triggers rollback, and any checked Exceptions do not. |
propagation
These are the transaction propagation behaviours defined by the propagation
enum. Let’s look at a couple of them.
REQUIRED
- Supports a current transaction, create a new one if none exists.
If there is a transaction already started, then this method will execute within that, otherwise, a new one will be created.
REQUIRES_NEW
- Create a new transaction, and suspend the current transaction if one exists.
readOnly
The next attribute I want to look at is readOnly
, which is a boolean.
What happens when the read-only attribute is set to true
?
Spring doesn’t handle persistence, so it cannot define exactly what read-only
should do. So this is just a hint to the provider which in this case is hibernate. According to the documentation, if using hibernate as the JPA provider, when readOnly
flag is set to true
, flushMode
on the Hibernate session will be set to NEVER, preventing any changes to data.
Following is the excerpt from the spring data documentation which states this.
“It’s definitely reasonable to use transactions for read only queries and we can mark them as such by setting the readOnly flag. This will not, however, act as check that you do not trigger a manipulating query (although some databases reject INSERT and UPDATE statements inside a read only transaction). The readOnly flag instead is propagated as hint to the underlying JDBC driver for performance optimizations. Furthermore, Spring will perform some optimizations on the underlying JPA provider. E.g. when used with Hibernate the flush mode is set to NEVER when you configure a transaction as readOnly which causes Hibernate to skip dirty checks (a noticeable improvement on large object trees).” — Spring Documentation
timeout
timeout
is the number of seconds before a transaction times out.
javax.transaction.Transactional vs org.springframework.transaction.annotation.Transactional
Spring transaction management also supports the @Transactional
annotation from Java (javax.transaction.Transactional
) as a drop-in replacement for the @Transactional
annotation provided by Spring. However, it lacks some of the settings available in the one from Spring such as readOnly
and timeout
which are quite useful. So I would use Spring’s @Transactional
annotation instead of the one from Java.
What Can Be Annotated With @Transactional?
The @Transactional
annotation can be placed on interfaces, classes, or both class and interface methods.
“Spring recommends that you only annotate concrete classes (and methods of concrete classes) with the
@Transactional
annotation, as opposed to annotating interfaces. You certainly can place the@Transactional
annotation on an interface (or an interface method), but this works only as you would expect it to if you are using interface-based proxies. The fact that Java annotations are not inherited from interfaces means that if you are using class-based proxies ( proxy-target-class=”true”) or the weaving-based aspect ( mode=”aspectj”), then the transaction settings are not recognized by the proxying and weaving infrastructure, and the object will not be wrapped in a transactional proxy, which would be decidedly bad.”
— Spring Documentation
Don’t worry if you are confused with the above excerpt, it’s all explained below.
Transaction Configuration In Spring
In a typical Spring application, configuration could be done via XML or Java/annotations.
XML Based configuration
When using XML based configurations, the following line in the config file would enable annotation-driven transaction management.
<!-- enable the configuration of transactional behavior based on annotations -->
<tx:annotation-driven transaction-manager="txManager"/>
Following are the settings or the attributes which can be set on this tag.
transaction-manager
mode
proxy-target-class
order
Java Based configuration
When using java based configuration, @EnableTransactionManagement
annotation would be used to provide the same configuration as above.
It has the following three optional elements.
mode
order
proxyTargetClass
Regardless of the mode of configuration, i.e. either XML or Java based configuration, so long as we have not specifically set these attributes, the default values apply. So let’s look at each one.
The proxyTargetClass or proxy-target-class
This could be either true or false.
The default value is false. In which case, JDK interface-based proxies are created.
If this attribute is set to true, then CGLIB proxies will be used, which are class-based, and therefore any @Transactional
annotations on interfaces will be ignored.
The mode
There are 2 values that can be applied to the mode
attribute. proxy
and aspectJ
.
The default value of the mode attribute is proxy
. It processes annotated beans to be proxied using Spring’s AOP framework, which would proxy interfaces annotated with @Transactional
.
But when the mode is set to aspectJ
the interfaces annotated with @Transactional
will be ignored.
That’s because,
The aspect that interprets @Transactional annotations is the AnnotationTransactionAspect. When using this aspect, you must annotate the implementation class (and/or methods within that class), not the interface (if any) that the class implements. AspectJ follows Java’s rule that annotations on interfaces are not inherited. — Spring Documentation
Keep in mind when using proxy
mode which is the default setting.
-
only external method calls will be transactional.
even though a method is marked with
@Transactional
annotation, if it is called within another method of the same object, it will not have transactional behaviour. -
you should apply the
@Transactional
annotation only to methods with public visibility.If you do annotate protected, private or package-visible methods with the
@Transactional
annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings.
So if you want transactional behaviour in either self invocations or non-public methods, consider the use of aspectJ
mode instead of proxy
.
What Happens In A Spring Boot Application
In a Spring Boot application, transaction management is enabled by the framework, without having to add the @EnableTransactionManagement
annotation, and the defaults are applied.
Hence there is no restriction as to where to place the @Transactional
annotations, but it is safer to follow the recommendation because in case those need to be changed later on.
I highly recommend reading the documentation for Spring transaction management as we cannot cover everything here.