Spring6 integrates JUnit5 and 1 transaction JdbcTemplate

6. Unit testing: JUnit

In the previous test method, you can almost see the following two lines of code:

ApplicationContext context = new ClassPathXmlApplicationContext("xxx.xml");
Xxxx xxx = context.getBean(Xxxx.class);

The function of these two lines of code is to create a Spring container and finally get the object, but each test needs to be written repeatedly. To solve the above problems, what we need is that the program can automatically create containers for us. We all know that JUnit cannot know whether we use the Spring framework, let alone help us create a Spring container. Spring provides a runner that can read configuration files (or annotations) to create containers. We just need to tell it the configuration file location. In this way, we can make the program create a spring container by integrating JUnit through Spring

6.1. Integrate JUnit5

6.1.1. Building sub-modules

Build spring-junit module

6.1.2. Introducing dependencies

<dependencies>
    <!--spring context依赖-->
    <!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>6.0.2</version>
    </dependency>

    <!--spring对junit的支持相关依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>6.0.2</version>
    </dependency>

    <!--junit5测试-->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>5.9.0</version>
    </dependency>

    <!--log4j2的依赖-->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.19.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-slf4j2-impl</artifactId>
        <version>2.19.0</version>
    </dependency>
</dependencies>

6.1.3. Add configuration file

beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="com.atguigu.spring6.bean"/>
</beans>

copy log file: log4j2.xml

6.1.4, add java class

package com.atguigu.spring6.bean;

import org.springframework.stereotype.Component;

@Component
public class User {
    
    

    public User() {
    
    
        System.out.println("run user");
    }
}

6.1.5. Test

import com.atguigu.spring6.bean.User;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

//两种方式均可
//方式一
//@ExtendWith(SpringExtension.class)
//@ContextConfiguration("classpath:beans.xml")
//方式二
@SpringJUnitConfig(locations = "classpath:beans.xml")
public class SpringJUnit5Test {
    
    

    @Autowired
    private User user;

    @Test
    public void testUser(){
    
    
        System.out.println(user);
    }
}

6.2. Integrate JUnit4

JUnit4 is also often used in the company, so learn about it here

6.2.1. Add dependencies

<!-- junit测试 -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

6.2.2. Test

import com.atguigu.spring6.bean.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:beans.xml")
public class SpringJUnit4Test {
    
    

    @Autowired
    private User user;

    @Test
    public void testUser(){
    
    
        System.out.println(user);
    }
}

7. Affairs

7.1、JdbcTemplate

7.1.1 Introduction

insert image description here

The Spring framework encapsulates JDBC and uses JdbcTemplate to facilitate database operations

7.1.2. Preparations

① Build sub-modules

Build submodules: spring-jdbc-tx

②Add dependency

<dependencies>
    <!--spring jdbc  Spring 持久化层支持jar包-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>6.0.2</version>
    </dependency>
    <!-- MySQL驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.30</version>
    </dependency>
    <!-- 数据源 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.15</version>
    </dependency>
</dependencies>

③ Create jdbc.properties

jdbc.user=root
jdbc.password=root
jdbc.url=jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false
jdbc.driver=com.mysql.cj.jdbc.Driver

④ Configure the configuration file of Spring

beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 导入外部属性文件 -->
    <context:property-placeholder location="classpath:jdbc.properties" />

    <!-- 配置数据源 -->
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="username" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!-- 配置 JdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!-- 装配数据源 -->
        <property name="dataSource" ref="druidDataSource"/>
    </bean>

</beans>

⑤ Prepare the database and test table

CREATE DATABASE `spring`;

use `spring`;

CREATE TABLE `t_emp` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL COMMENT '姓名',
  `age` int(11) DEFAULT NULL COMMENT '年龄',
  `sex` varchar(2) DEFAULT NULL COMMENT '性别',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

7.1.3. Realize CURD

①Assembly JdbcTemplate

Create a test class, integrate JUnit, and inject JdbcTemplate

package com.atguigu.spring6;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

@SpringJUnitConfig(locations = "classpath:beans.xml")
public class JDBCTemplateTest {
    
    

    @Autowired
    private JdbcTemplate jdbcTemplate;
    
}
② Test addition, deletion and modification functions
@Test
//测试增删改功能
public void testUpdate(){
    
    
    //添加功能
	String sql = "insert into t_emp values(null,?,?,?)";
	int result = jdbcTemplate.update(sql, "张三", 23, "男");
    
    //修改功能
	//String sql = "update t_emp set name=? where id=?";
    //int result = jdbcTemplate.update(sql, "张三atguigu", 1);

    //删除功能
	//String sql = "delete from t_emp where id=?";
	//int result = jdbcTemplate.update(sql, 1);
}
③ query data return object
public class Emp {
    
    

    private Integer id;
    private String name;
    private Integer age;
    private String sex;

    //生成get和set方法
    //......

    @Override
    public String toString() {
    
    
        return "Emp{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                '}';
    }
}
//查询:返回对象
@Test
public void testSelectObject() {
    
    
    //写法一
//        String sql = "select * from t_emp where id=?";
//        Emp empResult = jdbcTemplate.queryForObject(sql,
//                (rs, rowNum) -> {
    
    
//                    Emp emp = new Emp();
//                    emp.setId(rs.getInt("id"));
//                    emp.setName(rs.getString("name"));
//                    emp.setAge(rs.getInt("age"));
//                    emp.setSex(rs.getString("sex"));
//                    return emp;
//                }, 1);
//        System.out.println(empResult);

    //写法二
    String sql = "select * from t_emp where id=?";
    Emp emp = jdbcTemplate.queryForObject(sql,
                  new BeanPropertyRowMapper<>(Emp.class),1);
    System.out.println(emp);
}
④Query data and return list collection
@Test
//查询多条数据为一个list集合
public void testSelectList(){
    
    
    String sql = "select * from t_emp";
    List<Emp> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Emp.class));
    System.out.println(list);
}
⑤ The query returns a single value
@Test
//查询单行单列的值
public void selectCount(){
    
    
    String sql = "select count(id) from t_emp";
    Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
    System.out.println(count);
}

7.2. Declarative transaction concept

7.2.1. Basic concept of transaction

① What is a transaction

A database transaction is a sequence of database operations that access and possibly manipulate various data items. These operations are either all executed or not executed at all, and are an inseparable unit of work. A transaction consists of all database operations performed between the start of the transaction and the end of the transaction.

②Characteristics of business

A: Atomicity

All operations in a transaction are either all completed or not completed, and will not end at a certain link in the middle. If an error occurs during the execution of the transaction, it will be rolled back (Rollback) to the state before the transaction started, as if the transaction had never been executed.

C: Consistency

Transactional consistency means that the database must be in a consistent state before and after a transaction is executed.

If the transaction completes successfully, then all changes in the system will be applied correctly and the system is in a valid state.

If an error occurs in the transaction, all changes in the system will be rolled back automatically, and the system returns to the original state.

I: Isolation

It means that in a concurrent environment, when different transactions manipulate the same data at the same time, each transaction has its own complete data space. Modifications made by concurrent transactions must be isolated from modifications made by any other concurrent transaction. When a transaction views data updates, the state of the data is either the state before another transaction modifies it, or the state after another transaction modifies it, and the transaction will not view the data in the intermediate state.

D: Durability

It means that as long as the transaction ends successfully, the updates it makes to the database must be preserved. Even if a system crash occurs, after restarting the database system, the database can be restored to the state when the transaction ended successfully.

7.2.2. Programmatic transactions

The related operations of the transaction function are all realized by writing code yourself:

Connection conn = ...;
    
try {
    
    
    
    // 开启事务:关闭事务的自动提交
    conn.setAutoCommit(false);
    
    // 核心操作
    
    // 提交事务
    conn.commit();
    
}catch(Exception e){
    
    
    
    // 回滚事务
    conn.rollBack();
    
}finally{
    
    
    
    // 释放数据库连接
    conn.close();
    
}

The programmatic implementation has flaws:

  • The details are not shielded: in the specific operation process, all the details need to be completed by the programmer himself, which is cumbersome.
  • Code reusability is not high: If it is not effectively extracted, you need to write your own code every time you implement a function, and the code will not be reused.

7.2.3. Declarative transactions

Since the transaction control code has rules to follow and the structure of the code is basically determined, the framework can extract the code with a fixed pattern and perform related packaging.

After packaging, we only need to make a simple configuration in the configuration file to complete the operation.

  • Benefit 1: Improve development efficiency
  • Benefit 2: Eliminate redundant code
  • Benefit 3: The framework will comprehensively consider various problems that may be encountered in the actual development environment in related fields, and optimize robustness, performance and other aspects

Therefore, we can summarize the following two concepts:

  • Programmatic : Write your own code to realize the function
  • Declarative : Let the framework implement functions through configuration

7.3. Annotation-based declarative transactions

7.3.1. Preparations

① Add configuration

Add configuration in beans.xml

<!--扫描组件-->
<context:component-scan base-package="com.atguigu.spring6"></context:component-scan>

② create a table

CREATE TABLE `t_book` (
  `book_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `book_name` varchar(20) DEFAULT NULL COMMENT '图书名称',
  `price` int(11) DEFAULT NULL COMMENT '价格',
  `stock` int(10) unsigned DEFAULT NULL COMMENT '库存(无符号)',
  PRIMARY KEY (`book_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
insert  into `t_book`(`book_id`,`book_name`,`price`,`stock`) values (1,'斗破苍穹',80,100),(2,'斗罗大陆',50,100);
CREATE TABLE `t_user` (
  `user_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `username` varchar(20) DEFAULT NULL COMMENT '用户名',
  `balance` int(10) unsigned DEFAULT NULL COMMENT '余额(无符号)',
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
insert  into `t_user`(`user_id`,`username`,`balance`) values (1,'admin',50);

③ Create components

Create BookController:

package com.atguigu.spring6.controller;

@Controller
public class BookController {
    
    

    @Autowired
    private BookService bookService;

    public void buyBook(Integer bookId, Integer userId){
    
    
        bookService.buyBook(bookId, userId);
    }
}

Create interface BookService:

package com.atguigu.spring6.service;
public interface BookService {
    
    
    void buyBook(Integer bookId, Integer userId);
}

Create the implementation class BookServiceImpl:

package com.atguigu.spring6.service.impl;
@Service
public class BookServiceImpl implements BookService {
    
    

    @Autowired
    private BookDao bookDao;

    @Override
    public void buyBook(Integer bookId, Integer userId) {
    
    
        //查询图书的价格
        Integer price = bookDao.getPriceByBookId(bookId);
        //更新图书的库存
        bookDao.updateStock(bookId);
        //更新用户的余额
        bookDao.updateBalance(userId, price);
    }
}

Create interface BookDao:

package com.atguigu.spring6.dao;
public interface BookDao {
    
    
    Integer getPriceByBookId(Integer bookId);

    void updateStock(Integer bookId);

    void updateBalance(Integer userId, Integer price);
}

Create the implementation class BookDaoImpl:

package com.atguigu.spring6.dao.impl;
@Repository
public class BookDaoImpl implements BookDao {
    
    

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public Integer getPriceByBookId(Integer bookId) {
    
    
        String sql = "select price from t_book where book_id = ?";
        return jdbcTemplate.queryForObject(sql, Integer.class, bookId);
    }

    @Override
    public void updateStock(Integer bookId) {
    
    
        String sql = "update t_book set stock = stock - 1 where book_id = ?";
        jdbcTemplate.update(sql, bookId);
    }

    @Override
    public void updateBalance(Integer userId, Integer price) {
    
    
        String sql = "update t_user set balance = balance - ? where user_id = ?";
        jdbcTemplate.update(sql, price, userId);
    }
}

7.3.2, test no transaction

① Create a test class

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

@SpringJUnitConfig(locations = "classpath:beans.xml")
public class TxByAnnotationTest {
    
    

    @Autowired
    private BookController bookController;

    @Test
    public void testBuyBook(){
    
    
        bookController.buyBook(1, 1);
    }

}

②Simulation scene

When a user purchases a book, first query the price of the book, and then update the inventory of the book and the balance of the user

Suppose the user with user id 1 purchases the book with id 1

User balance is 50 and book price is 80

After purchasing the book, the user's balance is -30, and the balance field in the database is set to unsigned, so -30 cannot be inserted into the balance field

Executing the sql statement at this time will throw SQLException

③Observation results

Because no transaction was added, the book's inventory was updated, but the user's balance was not updated

Obviously such a result is wrong, purchasing books is a complete function, updating inventory and updating balance either succeeds or both fail

7.3.3, join the transaction

①Add transaction configuration

Introduce the tx namespace in the spring configuration file

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd">

Add configuration to Spring's configuration file:

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="druidDataSource"></property>
</bean>

<!--
    开启事务的注解驱动
    通过注解@Transactional所标识的方法或标识的类中所有的方法,都会被事务管理器管理事务
-->
<!-- transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id正好就是这个默认值,则可以省略这个属性 -->
<tx:annotation-driven transaction-manager="transactionManager" />
② Add transaction notes

Because the service layer represents the business logic layer, and a method represents a completed function, transactions are generally processed in the service layer

Add annotation @Transactional to buybook() of BookServiceImpl

③Observation results

Due to the use of Spring's declarative transactions, neither update inventory nor update balance are executed

7.3.4. Position of @Transactional annotation mark

@Transactional is marked on the method, it will only affect the method

On the class identified by @Transactional, it will affect all methods in the class

7.3.5. Transaction attribute: read-only

① Introduction

For a query operation, if we set it to read-only, we can clearly tell the database that this operation does not involve write operations. This allows the database to be optimized for query operations.

②How to use

@Transactional(readOnly = true)
public void buyBook(Integer bookId, Integer userId) {
    
    
    //查询图书的价格
    Integer price = bookDao.getPriceByBookId(bookId);
    //更新图书的库存
    bookDao.updateStock(bookId);
    //更新用户的余额
    bookDao.updateBalance(userId, price);
    //System.out.println(1/0);
}

③Attention

Setting read-only for addition, deletion, and modification operations will throw the following exception:

Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed

7.3.6. Transaction attribute: timeout

① Introduction

During the execution of the transaction, the program may be stuck due to certain problems, thus occupying database resources for a long time. However, if resources are occupied for a long time, there is a high probability that there is a problem with the running of the program (it may be a Java program or a MySQL database or a network connection, etc.). At this time, the program that is likely to have problems should be rolled back, undo the operations it has done, end the transaction, release resources, and allow other normal programs to execute.

In a nutshell, it is one sentence: roll back when timeout, release resources.

②How to use

//超时时间单位秒
@Transactional(timeout = 3)
public void buyBook(Integer bookId, Integer userId) {
    
    
    try {
    
    
        TimeUnit.SECONDS.sleep(5);
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
    //查询图书的价格
    Integer price = bookDao.getPriceByBookId(bookId);
    //更新图书的库存
    bookDao.updateStock(bookId);
    //更新用户的余额
    bookDao.updateBalance(userId, price);
    //System.out.println(1/0);
}

③Observation results

An exception is thrown during execution:

org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Fri Jun 04 16:25:39 CST 2022

7.3.7, transaction attribute: rollback strategy

① Introduction

By default, declarative transactions are only rolled back for runtime exceptions, and compile-time exceptions are not rolled back.

The rollback strategy can be set through the relevant attributes in @Transactional

  • rollbackFor attribute: need to set an object of type Class

  • rollbackForClassName attribute: need to set a full class name of string type

  • noRollbackFor attribute: need to set an object of type Class

  • rollbackFor attribute: need to set a full class name of string type

②How to use

@Transactional(noRollbackFor = ArithmeticException.class)
//@Transactional(noRollbackForClassName = "java.lang.ArithmeticException")
public void buyBook(Integer bookId, Integer userId) {
    
    
    //查询图书的价格
    Integer price = bookDao.getPriceByBookId(bookId);
    //更新图书的库存
    bookDao.updateStock(bookId);
    //更新用户的余额
    bookDao.updateBalance(userId, price);
    System.out.println(1/0);
}

③Observation results

Although a mathematical operation exception (ArithmeticException) occurs in the function of purchasing books, the rollback strategy we set is that when an ArithmeticException occurs, rollback does not occur, so the operation of purchasing books is performed normally

7.3.8, transaction attributes: isolation level

① Introduction

The database system must have the ability to isolate and run various transactions concurrently, so that they will not affect each other and avoid various concurrency problems. The degree to which a transaction is isolated from other transactions is called an isolation level. The SQL standard specifies multiple transaction isolation levels. Different isolation levels correspond to different interference levels. The higher the isolation level, the better the data consistency, but the weaker the concurrency.

There are four isolation levels:

  • Read uncommitted: READ UNCOMMITTED

    Allow Transaction01 to read uncommitted modifications of Transaction02.

  • Read committed: READ COMMITTED,

    It is required that Transaction01 can only read the modification submitted by Transaction02.

  • Repeatable read: REPEATABLE READ

    Ensure that Transaction01 can read the same value from a field multiple times, that is, prohibit other transactions from updating this field during the execution of Transaction01.

  • Serialization: SERIALIZABLE

    Ensure that Transaction01 can read the same row from a table multiple times. During the execution of Transaction01, prohibit other transactions from adding, updating, and deleting operations on this table. Any concurrency issues are avoided, but performance is very poor.

The ability of each isolation level to solve concurrency problems is shown in the following table:

isolation level dirty read non-repeatable read Phantom reading
READ UNCOMMITTED have have have
READ COMMITTED none have have
REPEATABLE READ none none have
SERIALIZABLE none none none

The degree to which various database products support transaction isolation levels:

isolation level Oracle MySQL
READ UNCOMMITTED ×
READ COMMITTED √(default)
REPEATABLE READ × √(default)
SERIALIZABLE

②How to use

@Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
@Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
@Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
@Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
@Transactional(isolation = Isolation.SERIALIZABLE)//串行化

7.3.9. Transaction attribute: propagation behavior

① Introduction

What is the propagation behavior of a transaction?

There are a() method and b() method in the service class, there is a transaction on the a() method, and there is also a transaction on the b() method. When the b() method is called during the execution of the a() method, how does the transaction pass of? merged into one transaction? Or start a new transaction? This is transaction propagation behavior.

There are seven communication behaviors:

  • REQUIRED: support the current transaction, if it does not exist, create a new one (default) [create if there is no, join if there is]
  • SUPPORTS: Support the current transaction, if there is no current transaction, it will be executed in a non-transactional way **【Join if you have it, don’t care if you don’t have it】**
  • MANDATORY: must run in a transaction, if no transaction is currently happening, an exception will be thrown **[join if there is one, throw an exception if there is no]**
  • REQUIRES_NEW: Open a new transaction. If a transaction already exists, suspend the existing transaction. Transaction is suspended]**
  • NOT_SUPPORTED: Run in a non-transactional mode, if there is a transaction, suspend the current transaction **[do not support transactions, suspend if they exist]**
  • NEVER: Run in a non-transactional mode, if there is a transaction, throw an exception **[does not support transactions, throws an exception if it exists]**
  • NESTED: If a transaction is currently in progress, the method should run in a nested transaction. Nested transactions can be committed or rolled back independently of the outer transaction. If the outer transaction does not exist, behave as REQUIRED. [If there is a transaction, nest a completely independent transaction in this transaction, and the nested transaction can be submitted and rolled back independently. No transaction is the same as REQUIRED.

② test

Create interface CheckoutService:

package com.atguigu.spring6.service;

public interface CheckoutService {
    
    
    void checkout(Integer[] bookIds, Integer userId);
}

Create the implementation class CheckoutServiceImpl:

package com.atguigu.spring6.service.impl;

@Service
public class CheckoutServiceImpl implements CheckoutService {
    
    

    @Autowired
    private BookService bookService;

    @Override
    @Transactional
    //一次购买多本图书
    public void checkout(Integer[] bookIds, Integer userId) {
    
    
        for (Integer bookId : bookIds) {
    
    
            bookService.buyBook(bookId, userId);
        }
    }
}

Add method in BookController:

@Autowired
private CheckoutService checkoutService;

public void checkout(Integer[] bookIds, Integer userId){
    
    
    checkoutService.checkout(bookIds, userId);
}

Modify the user's balance to 100 yuan in the database

③Observation results

Transaction propagation behavior can be set through the propagation attribute in @Transactional

Modify the propagation attribute of @Transactional on buyBook() in BookServiceImpl

@Transactional(propagation = Propagation.REQUIRED), by default, means that if there is an already opened transaction available on the current thread, it will run in this transaction. After observation, the method buyBook() for purchasing books is called in checkout(), and there are transaction annotations on checkout(), so it is executed in this transaction. The prices of the two books purchased are 80 and 50, and the user's balance is 100, so when the second book is purchased, the balance fails, causing the entire checkout() to roll back, that is, as long as one book cannot be purchased, it will be closed. can't buy

@Transactional(propagation = Propagation.REQUIRES_NEW), means that no matter whether there is an already opened transaction on the current thread, a new transaction must be opened. In the same scenario, each book purchase is executed in the buyBook() transaction, so the first book is purchased successfully, the transaction ends, the second book purchase fails, and only the second buyBook() is rolled back. The purchase of the first book will not be affected, that is, you can buy as many books as you can.

7.3.10, full annotation configuration transaction

①Add configuration class

package com.atguigu.spring6.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;

@Configuration
@ComponentScan("com.atguigu.spring6")
@EnableTransactionManagement
public class SpringConfig {
    
    

    @Bean
    public DataSource getDataSource(){
    
    
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        return dataSource;
    }

    @Bean(name = "jdbcTemplate")
    public JdbcTemplate getJdbcTemplate(DataSource dataSource){
    
    
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

    @Bean
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
    
    
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }
}

② test

import com.atguigu.spring6.config.SpringConfig;
import com.atguigu.spring6.controller.BookController;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

public class TxByAllAnnotationTest {
    
    

    @Test
    public void testTxAllAnnotation(){
    
    
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookController accountService = applicationContext.getBean("bookController", BookController.class);
        accountService.buyBook(1, 1);
    }
}

7.4. XML-based declarative transactions

7.3.1, scene simulation

See annotation-based declarative transactions

7.3.2, modify the Spring configuration file

Remove the tx:annotation-driven tag from the Spring configuration file and add the configuration:

<aop:config>
    <!-- 配置事务通知和切入点表达式 -->
    <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.atguigu.spring.tx.xml.service.impl.*.*(..))"></aop:advisor>
</aop:config>
<!-- tx:advice标签:配置事务通知 -->
<!-- id属性:给事务通知标签设置唯一标识,便于引用 -->
<!-- transaction-manager属性:关联事务管理器 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <!-- tx:method标签:配置具体的事务方法 -->
        <!-- name属性:指定方法名,可以使用星号代表多个字符 -->
        <tx:method name="get*" read-only="true"/>
        <tx:method name="query*" read-only="true"/>
        <tx:method name="find*" read-only="true"/>
    
        <!-- read-only属性:设置只读属性 -->
        <!-- rollback-for属性:设置回滚的异常 -->
        <!-- no-rollback-for属性:设置不回滚的异常 -->
        <!-- isolation属性:设置事务的隔离级别 -->
        <!-- timeout属性:设置事务的超时属性 -->
        <!-- propagation属性:设置事务的传播行为 -->
        <tx:method name="save*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
        <tx:method name="update*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
        <tx:method name="delete*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
    </tx:attributes>
</tx:advice>

Note: declarative transactions based on xml must introduce aspectJ dependencies

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>6.0.2</version>
</dependency>

Guess you like

Origin blog.csdn.net/2201_75381449/article/details/129747150