@Transactional in Spring Data JPA

@Transactional in Spring Data JPA | Transactions is a database concept (not specific to Spring Boot). It has two operations:-

  • commit
  • rollback

When Transactions are required? For Every non-select operation transactions are required.

  • save(obj) — INSERT
  • update(obj) — UPDATE
  • DELETE(obj) — DELETE

Transactions are not required for select operations. Here we have two functions / operations / commands:-

  • commit: It will update data from the buffer to the database (save it). By default in most of the database, it is auto-enable.
  • rollback: It will cancel operation/data from the buffer and not affect the database.

Buffers are part of RAM only.

MySQL Queries:-

mysql> use test;
mysql> show tables;
mysql> drop table student;
mysql> create table student(sid int,sname varchar(20), sfee double, scourse varchar(20));
mysql> set autocommit =0;

mysql> select * from student;
Empty set (0.00 sec)

mysql> insert into student values(10,'a',330,'JAVA');
mysql> select * from student;
+------+-------+------+---------+
| sid  | sname | sfee | scourse |
+------+-------+------+---------+
|   10 | a     |  330 | JAVA    |
+------+-------+------+---------+
1 row in set (0.00 sec)

mysql> rollback;
Query OK, 0 rows affected (0.01 sec)

mysql> select * from student;
Empty set (0.00 sec)

mysql> insert into student values(10,'a',330,'JAVA');
mysql> insert into student values(11,'B',430,'JAVA');
mysql> commit;
Query OK, 0 rows affected (0.01 sec)

mysql> select * from student;
+------+-------+------+---------+
| sid  | sname | sfee | scourse |
+------+-------+------+---------+
|   10 | a     |  330 | JAVA    |
|   11 | B     |  430 | JAVA    |
+------+-------+------+---------+
2 rows in set (0.00 sec)

mysql> insert into student values(12,'C',530,'JAVA');
Query OK, 1 row affected (0.00 sec)

mysql> select * from student;
+------+-------+------+---------+
| sid  | sname | sfee | scourse |
+------+-------+------+---------+
|   10 | a     |  330 | JAVA    |
|   11 | B     |  430 | JAVA    |
|   12 | C     |  530 | JAVA    |
+------+-------+------+---------+
3 rows in set (0.00 sec)

mysql> rollback;
Query OK, 0 rows affected (0.01 sec)

mysql> select * from student;
+------+-------+------+---------+
| sid  | sname | sfee | scourse |
+------+-------+------+---------+
|   10 | a     |  330 | JAVA    |
|   11 | B     |  430 | JAVA    |
+------+-------+------+---------+
2 rows in set (0.00 sec)

mysql> set autocommit =1;

Where is transaction management useful? When we are performing business operations and there are some connected queries, in that case, we must use transaction management.

Consider we have a table BankAccount with the following information:-

+------+-------+------+
| aid  | aname | bal  |
+------+-------+------+
|  101 | A     | 1000 |
|  102 | B     | 2000 |
+------+-------+------+

Transfer Money from A to B 200.0. Here, 200 must be deducted from account A (SQL#1) and 200 must be deposited to the account B (SQL#2). If one of them fails then both operations should be rolled back.

SQL#1:- UPDATE Account SET bal=bal-200 WHERE accId=101;
// Balance becomes 700

SQL#2:- UPDATE Account SET bal=bal+200 WHERE accId=102;
// Balance becomes 2200

Now either commit/rollback.

execute SQL#1 && SQL#2
if
    both success: commit
else
    any one fail: rollback

Let us see it through example:-

mysql> use test;
mysql> set autocommit=0;
mysql> create table account(aid int,aname varchar(10),bal double);
Query OK, 0 rows affected (0.03 sec)

mysql> insert into account values(101,'A',1000.0);
Query OK, 1 row affected (0.00 sec)

mysql> insert into account values(102,'B',2000.0);
Query OK, 1 row affected (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.01 sec)

mysql> select * from account;
+------+-------+------+
| aid  | aname | bal  |
+------+-------+------+
|  101 | A     | 1000 |
|  102 | B     | 2000 |
+------+-------+------+
2 rows in set (0.00 sec)

mysql> update account set bal=bal-200.0 where aid=101;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update account set bal=bal+200.0 where aid=105;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0  Changed: 0  Warnings: 0

mysql> select * from account;
+------+-------+------+
| aid  | aname | bal  |
+------+-------+------+
|  101 | A     |  800 |
|  102 | B     | 2000 |
+------+-------+------+
2 rows in set (0.00 sec)

mysql> rollback;
Query OK, 0 rows affected (0.01 sec)

mysql> select * from account;
+------+-------+------+
| aid  | aname | bal  |
+------+-------+------+
|  101 | A     | 1000 |
|  102 | B     | 2000 |
+------+-------+------+
2 rows in set (0.00 sec)

mysql> update account set bal=bal-200.0 where aid=101;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update account set bal=bal+200.0 where aid=102;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from account;
+------+-------+------+
| aid  | aname | bal  |
+------+-------+------+
|  101 | A     |  800 |
|  102 | B     | 2200 |
+------+-------+------+
2 rows in set (0.00 sec)

mysql> set autocommit=1;

In JDBC it is done as follows:-

try {
    Connection con= DriverManager.getConnection(..);
    con.setAutoCommit(false);

    SQL#1 (Prepared Stmt)
    SQL#2 (Prepared Stmt)

    con.commit(); //inform db to save results in in tables from buffer
} catch(SQLException exp) {
    con.rollback(); // cancel data from buffer;
}

In Hibernate it is done as follows:-

Transaction tx = null;
try {
    tx = session.beginTx();
    //operations..
    tx.commit();
} catch(Exception ex) {
    tx.rollback();
}

In Spring framework:-

In the Spring framework, it will be done as follows. Write logic inside the method and apply @Transactional.

@Transactional
void m1() {
    ......
}

Then internally Spring AOP code is executed for the method:-

afterReturningAdvice() // success
{
    tx.commit()
}
afterThrowingAdvice() // fail
{
    tx.rollback();
}

In Spring boot:-

In Spring Boot DO NOT WRITE CODE. Spring boot has provided PlatformTransactionManager.

PlatformTransactionManager(I)
IS-A
JpaTransactionManager(C)
EntityManagerFactory(I)
IS-A
SessionFactory(I)
IS-A
SessionFactoryDelegatingImpl(C)

@Transactional in Spring Data JPA

Key Concepts

  • Transaction: A sequence of operations performed as a single logical unit of work.
  • ACID Properties: Transactions must be Atomic, Consistent, Isolated, and Durable.
  • Transactional Annotation: Spring provides the @Transactional annotation to manage transactions declaratively.

How does it work?

  1. Configuration: Spring Boot automatically configures a transaction manager based on the data source you are using. For instance, with JPA, it typically configures a JpaTransactionManager. You can customize the transaction manager bean if needed.
  2. Annotation Usage: The @Transactional annotation can be applied at the method level or the class level. When applied at the class level, it applies to all public methods of the class.
@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;

    @Transactional
    public void createUser(User user) {
        userRepository.save(user);
    }

    @Transactional(readOnly = true)
    public User getUser(Long id) {
        return userRepository.findById(id).orElse(null);
    }
}
  1. Propagation and Isolation: The @Transactional annotation provides several attributes to control transaction behavior:-
    • propagation: Defines how transactions relate to each other. Common values are REQUIRED (default), REQUIRES_NEW, and NESTED.
    • isolation: Defines the isolation level of the transaction, such as READ_COMMITTED, READ_UNCOMMITTED, REPEATABLE_READ, and SERIALIZABLE.
  1. Rollback Rules: By default, transactions are rolled back on RuntimeException and Error but not on checked exceptions. You can customize this behavior using the rollbackFor and noRollbackFor attributes.
@Transactional(rollbackFor = Exception.class)
public void updateUser(User user) throws Exception {
    userRepository.save(user);
    if (someCondition) {
        throw new Exception("Forced rollback");
    }
}

Let us see it through an example:-

@Entity
@Getter
@Setter
@NoArgsConstructor
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String email;
}
public interface UserRepository extends JpaRepository<User, Long> { }
@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;

    @Transactional
    public void createUser(User user) {
        userRepository.save(user);
        // Additional logic here
    }

    @Transactional(readOnly = true)
    public User getUser(Long id) {
        return userRepository.findById(id).orElse(null);
    }

    @Transactional
    public void updateUserEmail(Long userId, String newEmail) {
        User user = userRepository.findById(userId).orElseThrow(() -> new EntityNotFoundException("User not found"));
        user.setEmail(newEmail);
        userRepository.save(user);
    }
}
@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody User user) {
        userService.createUser(user);
        return ResponseEntity.status(HttpStatus.CREATED).body(user);
    }

    @GetMapping("/{id}")
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        User user = userService.getUser(id);
        return ResponseEntity.ok(user);
    }

    @PutMapping("/{id}/email")
    public ResponseEntity<User> updateUserEmail(@PathVariable Long id, @RequestBody String newEmail) {
        userService.updateUserEmail(id, newEmail);
        return ResponseEntity.ok(userService.getUser(id));
    }
}

If you enjoyed this post, share it with your friends. Do you want to share more information about the topic discussed above or do you find anything incorrect? Let us know in the comments. Thank you!

Leave a Comment

Your email address will not be published. Required fields are marked *