Skip to main content
查利博客

Spring @Transactional Annotation

Before we talk about the Spring annoatation @Transactional, what exactly transaction is? Transaction represents a single unit of work which can be described by the ACID properties. ACID stands for Atomicity, Consistency, Isolation and Durability:

Atomicity: Operations performed on the data are either all successful or one.

Consistency: The data remains consistent state before and after the transaction.

Isolation: Transactions are isolated to each other. Check my previous article here about the transaction isolation in PostgreSQL.

**Durability: ** Successful commits should persist the data even in the event of a system error.

JDBC Transaction Management #

In Java, the Java Database Connectivity (JDBC) API provides transaction support from the java.sql.Connection interface. It provides the following three methods to perform transaction management.

By default, a JDBC connection is created in auto-commit mode. It means each sql statement is implicitly demarcated with a transaction. Hence, we need to manage transaction manually (by setting auto-commit false) if a particular service involves multiple sql statements.

JDBC Transaction Management Example:

mport java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class SimpleJDBCExample {
  public static void main(String[] args) {
    String url = "jdbc:postgresql://localhost/postgres";
    Properties props = new Properties();
    props.setProperty("user", "caizhenhua");
    props.setProperty("password", "123456");

    try (Connection conn = DriverManager.getConnection(url, props)) {
      try {
        conn.setAutoCommit(false);
        Statement stmt = conn.createStatement();
        String insertSql1 = "insert into t_user (id, username, password) values (33, 'test', '321')";
        String insertSql2 = "insert into t_user (id, username, password) values (33, 'test', '321')";
        stmt.executeUpdate(insertSql1);
        stmt.executeUpdate(insertSql2);
        conn.commit();
      } catch (SQLException e) {
        conn.rollback();
        throw e;
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

In the above example, no records are inserted because of duplicate key value violation. However, if auto-commit is enabled, the first update statement will success (data saved in database) while the second update statement throws exception. Let's see how spring @Transactional annotation ease the transaction management for developers.

Spring @Transactional Example #

This service class is basically equivalent to the above example except it is much cleaner and elegant.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserServiceWithTx {
  @Autowired
  private JdbcTemplate jdbcTemplate;

  @Transactional
  public void insertUser() {
    System.out.println("UserService#UserService begin");
    int i1 = jdbcTemplate.update("insert into t_user (id, username, password) values (37, 'test', '321')");
    int i2 = jdbcTemplate.update("insert into t_user (id, username, password) values (38, 'test', '321')");
    System.out.println("i1= " + i1);
    System.out.println("i2= " + i2);
    System.out.println("UserService#UserService end");
  }
}

Driver class using the spring CommandLineRunner interface.

import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class SpringTxRunner implements CommandLineRunner {
  private final UserServiceWithTx userService;

  // Setter Injection
  public SpringTxRunner(UserServiceWithTx userService) {
    this.userService = userService;
  }

  @Override
  public void run(String... args) throws Exception {
    System.out.println("SpringTxRunner#run begin");
    userService.insertUser();
    System.out.println("SpringTxRunner#run end");
  }
}

Basics of @Transactional #

The meta-anotations of @Transactional are defined as follows:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
	//...
}

@Transactional has total twelve optional elements, below is a simple example to illustrate all of them:

import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Transactional(
    isolation = Isolation.DEFAULT,
    label = {"label1, label2"},
    noRollbackFor = {RuntimeException.class, NullPointerException.class},
    noRollbackForClassName = {"java.lang.RuntimeException", "java.lang.NullPointerException"},
    propagation = Propagation.REQUIRED,
    readOnly = true,
    rollbackFor = {IllegalStateException.class},
    rollbackForClassName = {"java.lang.IllegalStateException"},
    timeout = 5,
    timeoutString = "5",
    transactionManager = "exampleTransactionManager",
    value = "exampleTransactionManager"
)
public class AllTransactionalElements {
}

As you can see, it is not that hard to configure the @Transactional annotation. But some properties should be carefully used for example the propagation, lable , transactionalManager or value. Overall, the benefits of Spring @Transactional are manifold: