Declarative Transactional Transactional for SpringBoot series tutorials

Declarative Transactional Transactional for the SpringBoot series of tutorials

When we want a set of operations to succeed or fail, we tend to consider using transactions to achieve this; the db operation described earlier, mainly on a single-table CURD, focuses on the use of declarative transactions@Transactional s

<!-- more -->

I. Configuration

This article mainly introduces the usage posture of jdbcTemplate with Transaction Note @Transactional. As for JPA, mybatis does not make much difference in actual usage, which will be explained separately later

Create a SpringBoot project, version 2.2.1.RELEASE, use mysql as the target database, store engine select Innodb, transaction isolation level RR

1. Project Configuration

In the project pom.xml file, plus spring-boot-starter-jdbc, a DataSourceTransactionManager bean is injected, providing transaction support

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

2. Database Configuration

Go to the spring configuration file application.properties to set up db-related information

## DataSource
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/story?useUnicode=true&characterEncoding=UTF-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=

3. Database

Create a new simple table structure for testing

CREATE TABLE `money` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL DEFAULT '' COMMENT 'User name',
  `money` int(26) NOT NULL DEFAULT '0' COMMENT 'money',
  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
  `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Creation Time',
  `update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Update Time',
  PRIMARY KEY (`id`),
  KEY `name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=551 DEFAULT CHARSET=utf8mb4;

II. Instructions for use

1. Initialization

In order to reflect the characteristics of transactions, deleting or modifications is an indispensable statement in DML regardless of DDL scenarios, so we need to initialize several data for testing first

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

    @PostConstruct
    public void init() {
        String sql = "replace into money (id, name, money) values (120, 'Initialization', 200)," +
                "(130, 'Initialization', 200)," +
                "(140, 'Initialization', 200)," +
                "(150, 'Initialization', 200)";
        jdbcTemplate.execute(sql);
    }
}

We use the replace into statement to initialize the data, which is executed after each bean creation, to ensure that the initial data is the same for each subsequent action you take

2. transactional

This annotation can be placed either on a class or on a method; if it is labeled on a class, all public methods of the class support transactions;

If both classes and methods exist, then the annotations on the methods have configuration to override the annotations on the classes

Here is a simple transaction test case

private boolean updateName(int id) {
    String sql = "update money set `name`='To update' where id=" + id;
    jdbcTemplate.execute(sql);
    return true;
}

public void query(String tag, int id) {
    String sql = "select * from money where id=" + id;
    Map map = jdbcTemplate.queryForMap(sql);
    System.out.println(tag + " >>>> " + map);
}

private boolean updateMoney(int id) {
    String sql = "update money set `money`= `money` + 10 where id=" + id;
    jdbcTemplate.execute(sql);
    return false;
}

/**
 * Run exception causes rollback
 *
 * @return
 */
@Transactional
public boolean testRuntimeExceptionTrans(int id) {
    if (this.updateName(id)) {
        this.query("after updateMoney name", id);
        if (this.updateMoney(id)) {
            return true;
        }
    }

    throw new RuntimeException("Update failed, rollback!");
}

Add the comment @Transactional to the public method where we need to open the transaction, indicating that if the method's internal execution throws a run exception, a transaction rollback will occur

Note the above statement that the transaction will not take effect until the correct call posture is taken; in other words, it will not take effect in some case s

3. Testing

Next, to test whether the above method transaction is valid, we create a new Bean

@Component
public class TransactionalSample {
    @Autowired
    private SimpleDemo simpleService;

    public void testSimpleCase() {
        System.out.println("============ Transaction is working start ========== ");
        simpleService.query("transaction before", 130);
        try {
            // Transactions can work
            simpleService.testRuntimeExceptionTrans(130);
        } catch (Exception e) {
        }
        simpleService.query("transaction end", 130);
        System.out.println("============ Transaction is working end ========== \n");
    }
}

In the call above, the data before and after modification are printed, and if the transaction is working correctly, the two outputs should be identical

The actual output is as follows, verifying that the transaction is valid, and the intermediate operation to modify the name is rolled back

============ Transaction is working start ==========
transaction before >>>> {id=130, name=Initialization, money=200, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:21.0}
after updateMoney name >>>> {id=130, name=To update, money=200, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:22.0}
transaction end >>>> {id=130, name=Initialization, money=200, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:21.0}
============ Transaction is working end ==========

4. Notes

a. Scenarios applicable

When using annotated @Transactional declarative transactions, it mainly uses AOP to encapsulate the logic of the transaction through a proxy, so the scenario where AOP does not take effect also applies to scenarios where the transaction annotation does not take effect

Simply put, the following case s do not work

  • Decoration @Transactional on private method, not valid
  • Internal call, not valid
    • Examples are: an external call to the generic method m of service A, which calls the method m2 in this class that declares a transaction annotated, in which case the transaction does not take effect

b. Exception types

In addition, the comment @Transactional default only works for runtime exceptions, such as the following case s, which throw exceptions but do not work

@Transactional
public boolean testNormalException(int id) throws Exception {
    if (this.updateName(id)) {
        this.query("after updateMoney name", id);
        if (this.updateMoney(id)) {
            return true;
        }
    }

    throw new Exception("Declare Exceptions");
}

If you need it to take effect, you can use the rollbackFor property to specify the type of exception that triggers the rollback


@Transactional(rollbackFor = Exception.class)
public boolean testSpecialException(int id) throws Exception {
    if (this.updateName(id)) {
        this.query("after updateMoney name", id);
        if (this.updateMoney(id)) {
            return true;
        }
    }

    throw new IllegalArgumentException("Parameter Exception");
}

Test the two case s above

public void testSimpleCase() {
    System.out.println("============ Transaction is not valid start ========== ");
    simpleService.query("transaction before", 140);
    try {
        // Because a non-operational exception was thrown, it will not be rolled back
        simpleService.testNormalException(140);
    } catch (Exception e) {
    }
    simpleService.query("transaction end", 140);
    System.out.println("============ Transaction is not valid end ========== \n");


    System.out.println("============ Transaction Effective start ========== ");
    simpleService.query("transaction before", 150);
    try {
        // In the comment, specify that all exceptions are rolled back
        simpleService.testSpecialException(150);
    } catch (Exception e) {
    }
    simpleService.query("transaction end", 150);
    System.out.println("============ Transaction Effective end ========== \n");
}

The output is as follows, which just validates what was mentioned above

============ Transaction is not valid start ==========
transaction before >>>> {id=140, name=Initialization, money=200, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:21.0}
after updateMoney name >>>> {id=140, name=To update, money=200, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:22.0}
transaction end >>>> {id=140, name=To update, money=210, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:22.0}
============ Transaction is not valid end ==========

============ Transaction Effective start ==========
transaction before >>>> {id=150, name=Initialization, money=200, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:21.0}
after updateMoney name >>>> {id=150, name=To update, money=200, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:22.0}
transaction end >>>> {id=150, name=Initialization, money=200, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:21.0}
============ Transaction Effective end ==========

c. @Transactional Attribute information for annotations

The above is a basic point of knowledge to meet our general business needs, if you need to be advanced, it is necessary to understand the attribute information

The following comes from: [A thorough understanding of Spring @transactional Use] ( https://www.cnblogs.com/xd502djj/p/10940627.html "A thorough grasp of Spring @transactional Use ")

Property Name Explain
name When there are multiple TransactionManager s in the configuration file, you can use this property to specify which Transaction Manager to select.
propagation The propagation behavior of the transaction, defaulting to REQUIRED.
isolation Transaction isolation with DEFAULT as the default.
timeout Transaction timeout, default value is -1.If the time limit is exceeded but the transaction has not been completed, the transaction is rolled back automatically.
read-only Specifies whether the transaction is read-only and the default value is false; to ignore methods that do not require transactions, such as reading data, you can set read-only to true.
rollback-for Used to specify exception types that trigger transaction rollback, and can be separated by commas if more than one exception type needs to be specified.
no-rollback- for Throws the exception type specified by no-rollback-for and does not roll back the transaction.

Please look forward to explaining the use of the above attributes and the circumstances under which declarative transactions will not take effect and new openings will be made.

II. Other

0.Series Blog & Source

Series Blog

Source code

1.A grey Blog

Unlike letters, the above are purely family statements. Due to limited personal abilities, there are unavoidable omissions and errors. If bug s are found or there are better suggestions, you are welcome to criticize and correct them with gratitude.

Below is a grey personal blog, which records all the blogs in study and work. Welcome to visit it

Tags: Programming Spring SQL SpringBoot MySQL

Posted on Sun, 19 Jan 2020 20:54:13 -0500 by avillanu