If you want to know how transaction and connection work in Spring Boot by “trying” with examples, this page is for you.
This example is related to Spring Boot with all examples. However, code in here is not in that link.
Full Code with Spring Boot 1.5.8.RELEASE:
8_spring-boot-test-db-transaction.zip
What you need for run this:
- Maven 3
- Java 8
- Mysql 5.x
What the code will do:
- The code will show you multiple examples of how to use connection and transaction in Spring.
- This page will explain those examples case by case.
Things you should know before start:
First, you should read this Spring Boot with Database, Transaction, JDBI example before. It will explain you about @Transactional and code structure in basic.
Second, there is a class named UniqueObjectIdHolder.java. This class looks like this:
package com.surasint.example.util; import java.util.IdentityHashMap; import java.util.Map; public class UniqueObjectIdHolder { private static Map<Object, Integer> map = new IdentityHashMap<>(); private static int count =0; public static int getId(Object object){ if (!map.containsKey(object)){ count++; map.put(object,count); } return map.get(object); } }
This class will just return an unique ID (just a number) of each object. It will be used to check which DB connection is using at a moment.
For example, UserDao.insert() has this code:
public List<UserBean> list(){ Connection conn = DataSourceUtils.getConnection(dataSource); System.out.println("userDao:list:datasource:"+ UniqueObjectIdHolder.getId(dataSource)); System.out.println("userDao:list:connection:"+ UniqueObjectIdHolder.getId(conn)); Handle handle = DBI.open(conn); UserSQLs userQLs = handle.attach(UserSQLs.class); return userQLs.list(); }
Line 3 and 4 show how to use UniqueObjectIdHolder. They are just printing unique IDs of “dataSource ” and “conn” object.
Prepare to run:
create a database named “testdb” and run dbscript.sql to the database in MySql:
CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(100) NOT NULL );
change application.properties to your MySql username and password:
#Database spring.datasource.url=jdbc:mysql://localhost/testdb spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.jdbc.Driver
run this in console:
mvn clean package
Then run this to start:
mvn clean spring-boot:run
You should see something like this:
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v1.5.8.RELEASE) ..... ..... 2017-12-12 22:03:04.243 INFO 260 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.Annotation ConfigEmbeddedWebApplicationContext@7595f8d7: startup date [Tue Dec 12 22:03:01 CET 2017]; root of context hierarchy
Open a browswer and try to go to this url:
http://localhost:8080/api/user/list
You should see this:

If you see this, that means it is working fine.
TIP: before trying with each case, you should clean all rows in database by call this:
http://localhost:8080/api/user/deleteAll
And check the row by calling “list” again:
http://localhost:8080/api/user/list
You will see nothing.
Case 1: Normal Insert
Let start with very basic one. You want to insert a row in database.
Try to call this url:
http://localhost:8080/api/user/insertOK
You will see a number, like this:

The code has nothing complex. It just calls TestRestController. insertOK() -> UserService.InsertOK() -> UserDao.insert(). The code looks like:
TestRestController:
@GetMapping("/api/user/insertOK") public Integer insertOK() { return userService.insertOK(); }
UserService:
@Transactional public Integer insertOK(){ UserBean test = new UserBean(); test.setUsername("username"+new Date().getTime()); return userDao.insert(test); }
UserDao:
public Integer insert(UserBean item){ Connection conn = DataSourceUtils.getConnection(dataSource); System.out.println("userDao:insert:datasource:"+ UniqueObjectIdHolder.getId(dataSource)); System.out.println("userDao:insertLconnection:"+ UniqueObjectIdHolder.getId(conn)); Handle handle = DBI.open(conn); UserSQLs userSQLs = handle.attach(UserSQLs.class); return userSQLs.insert(item); }
You will see these lines in the log or console:
userDao:insert:datasource:1 userDao:insertLconnection:5
Try to call the url again, then you will see other two new lines:
userDao:insert:datasource:1 userDao:insertLconnection:5 userDao:insert:datasource:1 userDao:insertLconnection:6
Note that “You get the same datasource but different connections” . In other words, Spring Boot gives you different connection in each request.
You can try to list the row by calling this url:
http://localhost:8080/api/user/list
You will see this:

Also note that, in the log, you will get different connection (see line 5 and 6):
userDao:insert:datasource:1 userDao:insertLconnection:5 userDao:insert:datasource:1 userDao:insertLconnection:6 userDao:list:datasource:1 userDao:list:connection:7
The most important is that the connection is closed by Spring Boot, you do not have to do anything. You can check by checking processes that connect to database (I suggest HeidiSQL, I have been using this nice lightweight MySql Client for 10 years. Please donate to the developer if you please).
Case 2: Exception and Rollback
Let say, you want to make sure that if there is something wrong, any insert/delete/update should be rollbacked.
We will call the code in UserService which looks like this:
@Transactional public void insertAndFail(){ UserBean test = new UserBean(); test.setUsername("username"+new Date().getTime()); userDao.insert(test); throw new RuntimeException("test fail"); }
This method will just insert a row in database but then it will throw a RuntimeException.
Try to run insertAndFail() by calling:
http://localhost:8080/api/user/insertNotOK
Then you will see this:

Try to check if the row is in database:
http://localhost:8080/api/user/list
You will see nothing.
So, Spring Boot takes care rollback for you. And again the connection is closed by Spring Boot, you do not have to do anything.
But BEWARE it will take care only “unchecked exception” such as RuntimeException. What happen if it is checked exception? It will not rollback, see the detail in the next case.
Anyways, you may ask what “checked exception” and “unchecked exception”. Here quick explanation:
- checked exception is the exception that you declare in method signature, for example:
public String myMethod() throws Exception {
- unchecked exception is the exception that you do not (do not have to) declare in method signature, for example RuntimeException or its subclass.
Case 3 : Exception but NOT rollback
If you have checked exception like this;
@Transactional public void insertAndFailButNotRollback() throws Exception { UserBean test = new UserBean(); test.setUsername("username"+new Date().getTime()); userDao.insert(test); throw new Exception("test fail"); }
The inserted row in line 5 will not be rollbacked.
You can try by calling:
http://localhost:8080/api/user/insertNotOKNotRollback
You will see this:

But the transaction is not rollbacked. You can check query in the database or by just calling this url:
http://localhost:8080/api/user/list
You may ask “Even it does not rollback, will the Spring boot close the connection for me?” Yes it will. You can check by checking processes that connect to database. They will not increase.
Then , how to make Spring Boot rollback the checked exception?
You can just change @Transactional to @Transactional(rollbackFor = Exception.class). Like this:
@Transactional(rollbackFor = Exception.class) public void insertAndFailAndRollback() throws Exception { UserBean test = new UserBean(); test.setUsername("username"+new Date().getTime()); userDao.insert(test); throw new Exception("test fail"); }
Try to call this url:
http://localhost:8080/api/user/insertNotOKAndRollback
You will see the error page and transaction also rollback.
Case 4 : Is it the same transaction ?
We know from the Case 1 that you will get different connection from the different request, but you may doubt that:
- Will I get the same connection across method call?
- Will I get the same connection across different DAO classes?
To answer the first question, in UserService, I have this method:
@Transactional public List<UserBean> listTwoTimes(){ userDao.list(); return userDao.list(); }
It will just call the same DAO object twice. The purpose of this is just to see if different method call still give use the same connection.
You can try by calling this url:
http://localhost:8080/api/user/listTwoTimes
You will see from the log that you will get the same ID of connection:
userDao:list:datasource:1 userDao:list:connection:11 userDao:list:datasource:1 userDao:list:connection:11
To answer the second question, in UserService, I have this method:
@Transactional public List<UserBean> listTwoDaos(){ userDao.list(); return userDao2.list(); }
It will call the TWO DAO objects. The purpose of this is to see if different object call still give use the same connection.
You can call the method by calling this url:
http://localhost:8080/api/user/listTwoDaos
You will see from the log that you will get the same ID of connection:
userDao:list:datasource:1 userDao:list:connection:12 userDao:list:datasource:1 userDao:list:connection:12
What you just know:
You should get some basic idea about how connection and transaction behave in Spring Boot.
You should see how powerful Spring Boot helps us about connection and transaction. “Great power comes with great responsibility” so before using it, you must understand it.
These cases should cover 90% (I guess) of basic needs of web applications with database.
Believe it or not, many developers do not know such this basic things and create a production-like applications. Database transaction is too important to just let some framework handles without understanding.