Introduction To Transaction In Spring
Sep 3, 2022 · 7 min readIn this post, we’ll implement a simple Spring app to understand transaction. By the end of this post, you’ll be equipped with the basic knowledge to work with transaction in Spring.
Let’s start the tutorial by introducing the concept of transaction. What exactly is a transaction?
What Is A Transaction
A transaction is a sequence of operations that are either executed successfully together or not at all. In the real-world scenario, transaction is often used to avoid data inconsistencies.
If any operations in the transaction were to fail, the transaction rolls back. In other words, the app is restored to how it was before the transaction happened.
If all the operations are executed successfully, all the changes that happened as a result of the transaction will be finalized. We often say that the transaction commits in this case.
Let’s look at a simple example of how transaction can be useful. Suppose you have a banking app, and it has the functionality of transferring money. The functionality implements these 2 steps:
- Withdraw money from the sender’s account.
- Deposit the money to the receiver’s account.
Imagine if the transaction at step 2 failed for some reason. What happened to the sender’s money?
This is where transaction comes into play. In this case, the transaction will roll back the app to how it was before step 1 is executed. The sender’s money will not be withdrawn. No harm done.
Using Transaction In Spring
To implement transaction in Spring, all you have to do is to put the @Transactional
annotation before a class or a method. If you put the method on a class, you tell Spring that every method in the class has to be transactional.
Note: If you are not using Spring Boot, you’ll have to add the
@EnableTransactionManagement
on your project’s configuration class.
Behind the scene, Spring will configure an aspect that intercepts all the methods annotated with @Transactional
. The aspect manages the transaction. If the transaction throws an exception, the changes will be rolled back, otherwise, they will be committed.
Implementing An Example App
To clear things up, We’ll create a simple app that mimics the scenario from the example above. Feel free to follow along or download the source code here.
Note: I assume that you know the basics of Spring Boot and how to interact with a database using JDBCTemplate, which you can find more here.
Generating App With Spring Initializr
First, use Spring Initializr to generate a Spring Boot project using these dependencies.
For simplicity’s sake, we will use H2 memory database, and use JDBCTemplate to interact with it.
Configuring H2 Database
We’ll keep the database simple. The database will contain a table called account
with 3 columns: id, name, and money.
Therefore, our schema.sql
in /src/main/resources/ will look like this:
We then fill in 2 users in our table. A sender and a receiver, each with $100 in their account. We put these queries inside data.sql
in /src/main/resources/.
Note: Spring Boot will automatically execute these 2 files when we start our app.
Create Model Object
From this session onwards, we’ll be working in the /src/main/java folder.
Next, let’s create an object that models our table’s row.
Create Repository Object
Next, we’ll create a repository object that’ll be responsible for interacting with the database.
This is the implementation for the AccountRowMapper
that we need to pass to jdbc.query
to map the result of our query into a format that our application knows how to work with.
Create Service Object
Since this service object contains logic that enables our app to transfer money, we’ll name it TransferService
. The class will receive an instance of AccountRepository
from the Spring context to interact with our database. And the logic will be inside the transferMoney()
method annotated with @Transactional
.
Create Controller Class
Now that we have implemented our logic for transferring money, the final step is to expose it with a Controller. Below is the code for our Controller class.
We also need a data transfer object called TransferRequest
that models our HTTP request body.
Running Our App
We’re now done implementing our app. Let’s run the app and try calling its endpoints with an API testing tool (Postman, cURL, etc.).
This is what I did:
- Send a POST request to the
/transfer
route, with the request body below.
- Send a GET request to the
/accounts
route, and get back the following JSON.
Seems like our app is working as expected. But how do we know if the @Transactional
annotation really works? We are going to prove that in the next section.
Testing The @Transaction
Annotation
To test if the @Transaction
annotation really works, let’s throw a RuntimeException
in our transferMoney()
method.
Now let’s restart the app and call the endpoints again.
- Send a POST request to the
/transfer
route, with the request body below.
- Send a GET request to the
/accounts
route. And unsurprisingly, the result is different from our first application run. The@Transactional
annotation did work as expected.
Because there’s an error, the transaction rollback our application. That’s why there’s no change at all. The app is just like it was before we execute the transferMoney()
method.
And if you check the log, you’ll find java.lang.RuntimeException: Problem executing transaction.
printed there.
Before we end the tutorial, let me introduce you to a common pitfall: Handling exceptions inside the method instead of throwing them.
This is a simple representation of the transaction aspect logic implemented by Spring.
If you handle the exception directly inside the method’s logic (part A), it won’t reach the catch block (part B). The aspect will never know that your method had an error, and thus, your changes will be committed, and no rollback will happen.
Wrap Up
Congrats on making it to the end. I hope this short tutorial gives you an idea of how transaction work, and how important it is in a real-world application.