Spring——事务

事务

JdbcTemplate

简介

Spring框架对JDBC进行封装,使用JdbcTemplate方便实现对数据库操作

准备工作

①搭建子模块

搭建子模块:spring-jdbc-tx

②加入依赖
<dependencies>
    <!--spring jdbc  Spring 持久化层支持jar包-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>6.1.14</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>
③创建jdbc.properties
jdbc.user=root
jdbc.password=123456
jdbc.url=jdbc:mysql://localhost:3306/spring?characterEncoding=utf-8&useUnicode=true&useSSL=true
jdbc.driver=com.mysql.cj.jdbc.Driver
④配置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 https://www.springframework.org/schema/context/spring-context.xsd">

    <!--引入外部文件,创建数据源对象-->
    <context:property-placeholder location="classpath:db.properties"/>

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

    <!--创建jdbcTemplate对象,注入数据源-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="druidDataSource"/>
    </bean>

</beans>
⑤准备数据库与测试表
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;

实现CRUD

①装配 JdbcTemplate
创建测试类,整合JUnit,注入JdbcTemplate
package com.ling.spring6.jdbc;

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
public void testUpdate() {
    /*
    // 1、添加操作
    // 第一步:编写sql语句
    String sql = "insert into t_emp(id,name,age,sex) values (NULL,?,?,?)";
    // 第二步:调用jdbcTemplate的方法,传递相关的参数
    Object[] params = {"张三", 20, "男"};
    int rows = jdbcTemplate.update(sql, params);
      int rows = jdbcTemplate.update(sql, "张三", 20, "男");
    System.out.println("受影响的行数:" + rows);
    */
    /*
    // 2、修改操作
    // 第一步:编写sql语句
    String sql = "update t_emp set name=?,age=?,sex=? where id=?";
    // 第二步:调用jdbcTemplate的方法,传递相关的参数
    Object[] params = {"小铃子", 18, "男", 1};
    int rows = jdbcTemplate.update(sql, params);
      int rows = jdbcTemplate.update(sql, "小铃子", 18, "男", 1);
    System.out.println("受影响的行数:" + rows);
     */
   // 3、删除操作
   // 第一步:编写sql语句
   String sql = "delete from t_emp where id = ?";
   // 第二步:调用jdbcTemplate的方法,传递相关的参数
    Object[] params = {3};
    int rows = jdbcTemplate.update(sql, params);
    System.out.println("受影响的行数:" + rows);
}
③查询数据返回对象
package com.ling.spring6.jdbc;

public class Emp {
    private int id;
    private String name;
    private int age;
    private String sex;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "Emp{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                '}';
    }
}
// 查询:返回对象
@Test
public void testSelectObject() {
    /*
    // 写法一
    // 1、编写sql语句
    String sql = "select id,name,age,sex from t_emp where id = ?";
    // 2、调用jdbcTemplate的方法,传递相关的参数
    Object[] params= {1};
    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;
    }, params);
    System.out.println(empResult);
     */
    // 写法二
    // 1、编写sql语句
    String sql = "select id,name,age,sex from t_emp where id = ?";
    // 2、调用jdbcTemplate的方法,传递相关的参数
    Emp emp = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Emp.class), 1);
    System.out.println(emp);
}
④查询数据返回list集合
// 查询:返回list集合
@Test
public void testSelectList() {
    // 1、编写sql语句
    String sql = "select id,name,age,sex from t_emp";
    // 2、调用jdbcTemplate的方法,传递相关的参数
    List<Emp> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Emp.class));
    System.out.println(list);
}
⑤查询返回单个的值
// 查询:返回单个值
@Test
public void testSelectValue() {
    // 1、编写sql语句
    String sql = "select count(*) from t_emp";
    // 2、调用jdbcTemplate的方法,传递相关的参数
    Integer i = jdbcTemplate.queryForObject(sql, Integer.class);
    System.out.println(i);
}

声明式事务管理

事务基本概念

①什么是事务

数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成。

②事务的特性

A:原子性(Atomicity)

一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

C:一致性(Consistency)

事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。

如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。

如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。

I:隔离性(Isolation)

指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。

D:持久性(Durability)

指的是只要事务成功结束,它对数据库所做的更新就必须保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。

编程式事务

事务功能的相关操作全部通过自己编写代码来实现:

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

编程式的实现方式存在缺陷:

  • 细节没有被屏蔽:具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐。
  • 代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码就没有得到复用。

声明式事务

既然事务控制的代码有规律可循,代码的结构基本是确定的,所以框架就可以将固定模式的代码抽取出来,进行相关的封装。

封装起来后,我们只需要在配置文件中进行简单的配置即可完成操作。

  • 好处1:提高开发效率
  • 好处2:消除了冗余的代码
  • 好处3:框架会综合考虑相关领域中在实际开发环境下有可能遇到的各种问题,进行了健壮性、性能等各个方面的优化

所以,我们可以总结下面两个概念:

  • 编程式自己写代码实现功能
  • 声明式:通过配置框架实现功能

基于注解的声明式事务

准备工作

①添加配置
在beans.xml添加配置
<!--开启组件扫描-->
<context:component-scan base-package="com.ling.spring6.tx"/>
②创建表
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);
③创建组件
创建BookController:
package com.ling.spring6.tx.controller;

import com.ling.spring6.tx.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class BookController {

    @Autowired
    private BookService bookService;

    // 买书的方法,图书id和用户id
    public void buyBok(int bookId, int userId) {
        // 调用service方法
        bookService.buyBook(bookId, userId);
    }
}
创建接口BookService:
package com.ling.spring6.tx.service;

public interface BookService {

    // 买书的方法
    void buyBook(int bookId, int userId);
}
创建实现类BookServiceImpl:
package com.ling.spring6.tx.service;

import com.ling.spring6.tx.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BookServiceImpl implements BookService {

    @Autowired
    private BookDao bookDao;

    @Override
    public void buyBook(int bookId, int userId) {
        // 根据图书id查询图书价格
        int price = bookDao.getBookPriceById(bookId);

        // 更新图书表库存量-1
        bookDao.updateStock(bookId);

        // 更新用户表用户余额-图书价格
        bookDao.updateUserBalance(userId, price);
    }
}
创建接口BookDao:
package com.ling.spring6.tx.dao;

public interface BookDao {
    // 根据id查询图书的价格
    int getBookPriceById(int bookId);

    // 根据id更新图书的库存
    void updateStock(int bookId);

    // 根据id更新用户的余额
    void updateUserBalance(int userId, int price);
}
创建实现类BookDaoImpl:
package com.ling.spring6.tx.dao;

import com.alibaba.druid.stat.JdbcSqlStatValue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class BookDaoImpl implements BookDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    // 根据图书id查询图书价格
    @Override
    public int getBookPriceById(int bookId) {
        String sql = "select price from t_book where book_id = ?";
        Integer price = jdbcTemplate.queryForObject(sql, int.class, bookId);
        return price;
    }

    // 根据图书id更新图书库存
    @Override
    public void updateStock(int bookId) {
        String sql = "update t_book set stock = stock - 1 where book_id = ?";
        jdbcTemplate.update(sql, bookId);
    }

    // 根据用户id更新用户余额
    @Override
    public void updateUserBalance(int userId, int price) {
        String sql = "update t_user set balance = balance - ? where user_id = ?";
        Object[] params = {price, userId};
        jdbcTemplate.update(sql, params);
    }
}

测试无事务情况

①创建测试类
package com.ling.spring6.tx;

import com.ling.spring6.tx.controller.BookController;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

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

    @Autowired
    private BookController bookController;

    @Test
    public void testBuyBook(){
        bookController.buyBok(1, 1);
    }
}
②模拟场景

用户购买图书,先查询图书的价格,再更新图书的库存和用户的余额

假设用户id为1的用户,购买id为1的图书

用户余额为50,而图书价格为80

购买图书之后,用户的余额为-30,数据库中余额字段设置了无符号,因此无法将-30插入到余额字段

此时执行sql语句会抛出SQLException

③观察结果

因为没有添加事务,图书的库存更新了,但是用户的余额没有更新

显然这样的结果是错误的,购买图书是一个完整的功能,更新库存和更新余额要么都成功要么都失败

加入事务

①添加事务配置
在spring配置文件中引入tx命名空间
<?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 
       https://www.springframework.org/schema/context/spring-context.xsd 
       http://www.springframework.org/schema/tx 
       http://www.springframework.org/schema/tx/spring-tx.xsd">
在Spring的配置文件中添加配置:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="druidDataSource"/>
</bean>
<!--
    开启事务的注解驱动
    通过注解@Transactional所标识的方法或标识的类中所有的方法,都会被事务管理器管理事务
-->
<!-- transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id正好就是这个默认值,则可以省略这个属性 -->
<tx:annotation-driven transaction-manager="transactionManager" />
②添加事务注解

因为service层表示业务逻辑层,一个方法表示一个完成的功能,因此处理事务一般在service层处理

在BookServiceImpl的buybook()添加注解@Transactional
package com.ling.spring6.tx.service;

import com.ling.spring6.tx.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Transactional
@Service
public class BookServiceImpl implements BookService {

    @Autowired
    private BookDao bookDao;

    // 买书的方法:图书id和用户id
//    @Transactional
    @Override
    public void buyBook(int bookId, int userId) {
        // 根据图书id查询图书价格
        int price = bookDao.getBookPriceById(bookId);

        // 更新图书表库存量-1
        bookDao.updateStock(bookId);

        // 更新用户表用户余额-图书价格
        bookDao.updateUserBalance(userId, price);
    }
}
③观察结果

由于使用了Spring的声明式事务,更新库存和更新余额都没有执行

@Transactional注解标识的位置

@Transactional标识在方法上,则只会影响该方法

@Transactional标识在类上,则会影响类中所有的方法

事务属性:只读

①介绍

对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作。这样数据库就能够针对查询操作来进行优化。

②使用方式
@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);
}
③注意

对增删改操作设置只读会抛出下面异常:

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

事务属性:超时
①介绍

事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间占用资源,大概率是因为程序运行出现了问题(可能是Java程序或MySQL数据库或网络连接等等)。此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常程序可以执行。

概括来说就是一句话:超时回滚,释放资源。

②使用方式
// 超时时间单位秒
@Transactional(timeout = 3)
@Override
public void buyBook(int bookId, int userId) {
    // TODO 模拟超时效果
    try {
        TimeUnit.SECONDS.sleep(5);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }

    // 根据图书id查询图书价格
    int price = bookDao.getBookPriceById(bookId);
    // 更新图书表库存量-1
    bookDao.updateStock(bookId);
    // 更新用户表用户余额-图书价格
    bookDao.updateUserBalance(userId, price);
}
③观察结果

org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Thu Nov 14 20:51:02 CST 2024

事务属性:回滚策略

①介绍

声明式事务默认只针对运行时异常回滚,编译时异常不回滚。

可以通过@Transactional中相关属性设置回滚策略

  • rollbackFor属性:需要设置一个Class类型的对象

  • rollbackForClassName属性:需要设置一个字符串类型的全类名

  • noRollbackFor属性:需要设置一个Class类型的对象

  • rollbackFor属性:需要设置一个字符串类型的全类名

②使用方式
@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);
}
③观察结果

虽然购买图书功能中出现了数学运算异常(ArithmeticException),但是我们设置的回滚策略是,当出现ArithmeticException不发生回滚,因此购买图书的操作正常执行

事务属性:隔离级别

①介绍

数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。

隔离级别一共有四种:

  • 读未提交:READ_UNCOMMITTED

    允许Transaction01读取Transaction02未提交的修改。

  • 读已提交:READ_COMMITTED、

    要求Transaction01只能读取Transaction02已提交的修改。

  • 可重复读:REPEATABLE_READ

    确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。

  • 串行化:SERIALIZABLE

    确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。

各个隔离级别解决并发问题的能力见下表:

隔离级别脏读不可重复读幻读
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE

各种数据库产品对事务隔离级别的支持程度:

隔离级别OracleMySQL
READ UNCOMMITTED×
READ COMMITTED√(默认)
REPEATABLE READ×√(默认)
SERIALIZABLE
②使用方式
@Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
@Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
@Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
@Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
@Transactional(isolation = Isolation.SERIALIZABLE)//串行化



事务属性:传播行为

①介绍

什么是事务的传播行为?

在service类中有a()方法和b()方法,a()方法上有事务,b()方法上也有事务,当a()方法执行过程中调用了b()方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务传播行为。

一共有七种传播行为:

  • REQUIRED:支持当前事务,如果不存在就新建一个(默认)【没有就新建,有就加入】
  • SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行【有就加入,没有就不管了】
  • MANDATORY:必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常【有就加入,没有就抛异常】
  • REQUIRES_NEW:开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起【不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起】
  • NOT_SUPPORTED:以非事务方式运行,如果有事务存在,挂起当前事务【不支持事务,存在就挂起】
  • NEVER:以非事务方式运行,如果有事务存在,抛出异常【不支持事务,存在就抛异常】
  • NESTED:如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像REQUIRED一样。【有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和REQUIRED一样。】
②测试
创建接口CheckoutService:
package com.ling.spring6.tx.service;

public interface CheckoutService {
    // 买多本书的方法
    void checkout(Integer[] bookIds,Integer userId);
}
创建实现类CheckoutServiceImpl:
package com.ling.spring6.tx.service;

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

@Service
public class CheckoutServiceImpl implements CheckoutService{
    // 注入bookService
    @Autowired
    private BookService bookService;

    // 买多本书的方法
    @Transactional
    @Override
    public void checkout(Integer[] bookIds, Integer userId) {
        for (Integer bookId : bookIds) {
            bookService.buyBook(bookId,userId);
        }
    }
}
在BookController中添加方法:
@Autowired
private CheckoutService checkoutService;

public void checkout(Integer[] bookIds, Integer userId){
    checkoutService.checkout(bookIds, userId);
}
在数据库中将用户的余额修改为100元
③观察结果

可以通过@Transactional中的propagation属性设置事务传播行为

修改BookServiceImpl中buyBook()上,注解@Transactional的propagation属性

@Transactional(propagation = Propagation.REQUIRED),默认情况,表示如果当前线程上有已经开启的事务可用,那么就在这个事务中运行。经过观察,购买图书的方法buyBook()在checkout()中被调用,checkout()上有事务注解,因此在此事务中执行。所购买的两本图书的价格为80和50,而用户的余额为100,因此在购买第二本图书时余额不足失败,导致整个checkout()回滚,即只要有一本书买不了,就都买不了

@Transactional(propagation = Propagation.REQUIRES_NEW),表示不管当前线程上是否有已经开启的事务,都要开启新事务。同样的场景,每次购买图书都是在buyBook()的事务中执行,因此第一本图书购买成功,事务结束,第二本图书购买失败,只在第二次的buyBook()中回滚,购买第一本图书不受影响,即能买几本就买几本。

全注解配置行为

①添加配置类
package com.ling.spring6.tx.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(value = "com.ling.spring6.tx") //扫描包
@EnableTransactionManagement //开启注解式事务管理
public class SpringConfig {

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

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }

    @Bean
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }
}
②测试
package com.ling.spring6.tx;

import com.ling.spring6.tx.config.SpringConfig;
import com.ling.spring6.tx.controller.BookController;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestAnno {

    @Test
    public void testAnno(){
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookController bookController = context.getBean("bookController", BookController.class);
        Integer[] bookIds = {1,2};
        bookController.checkout(bookIds, 1);
    }

}

基于XML的声明式事务

场景模拟

参考基于注解的声明式事务

修改Spring配置文件

将Spring配置文件中去掉tx:annotation-driven 标签,并添加配置:

<?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" 
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/tx
       https://www.springframework.org/schema/tx/spring-tx.xsd 
       http://www.springframework.org/schema/aop 
       https://www.springframework.org/schema/aop/spring-aop.xsd">

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

    <!--创建数据源对象-->
    <!--引入外部属性文件,创建数据源对象-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!--创建Druid数据池对象-->
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!--Template对象创建-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="druidDataSource"/>
    </bean>

    <!--事务管理器对象创建-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="druidDataSource"/>
    </bean>

    <!--配置事务增强-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="buy*" read-only="false" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

    <!--配置切入点和通知使用的方法-->
    <aop:config>
        <aop:pointcut id="pt" expression="execution(* com.ling.spring6.xmltx.service.BookServiceImpl.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
    </aop:config>


</beans>

注意:基于xml实现的声明式事务,必须引入aspectJ的依赖

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/916688.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

集合类源码浅析のJDK1.8ConcurrentHashMap(下篇)

文章目录 前言一、分段扩容1、addCount2、transfer3、helpTransfer 二、查询二、删除总结 前言 主要记录ConcurrentHashMap&#xff08;笔记中简称CHM&#xff09;的查询&#xff0c;删除&#xff0c;以及扩容方法的关键源码分析。 一、分段扩容 1、addCount 扩容的逻辑主要在…

H5页面多个视频如何只同时播放一个?

目录 背景1. 首先介绍下 muted 属性2. 监听播放和暂停操作3. 视频播放完毕后返回桌面&#xff0c;再进入H5页面发现视频封面丢失置灰解决思路&#xff1a; 背景 页面模块同时有个四个视频模块&#xff0c;发现可以同时播放四个视频&#xff0c;但是理想的是每次只播放一个。 …

D69【 python 接口自动化学习】- python 基础之数据库

day69 Python 执行 SQL 语句 学习日期&#xff1a;20241115 学习目标&#xff1a; MySQL 数据库&#xfe63;- Python连接redis 学习笔记&#xff1a; redis数据库的用途 使用Python访问redis数据库 使用Python对redis数据库进行读写操作 总结 1. redis是一款高性能的键…

jmeter常用配置元件介绍总结之逻辑控制器

系列文章目录 安装jmeter jmeter常用配置元件介绍总结之逻辑控制器 逻辑控制器1.IF控制器2.事务控制器3.循环控制器4.While控制器5.ForEach控制器6.Include控制器7.Runtime控制器8.临界部分控制器9.交替控制器10.仅一次控制器11.简单控制器12.随机控制器13.随机顺序控制器14.吞…

21.<基于Spring图书管理系统②(图书列表+删除图书+更改图书)(非强制登录版本完结)>

PS&#xff1a; 开闭原则 定义和背景‌ ‌开闭原则&#xff08;Open-Closed Principle, OCP&#xff09;‌&#xff0c;也称为开放封闭原则&#xff0c;是面向对象设计中的一个基本原则。该原则强调软件中的模块、类或函数应该对扩展开放&#xff0c;对修改封闭。这意味着一个软…

springboot实现简单的数据查询接口(无实体类)

目录 前言&#xff1a;springboot整体架构 1、ZjGxbMapper.xml 2、ZjGxbMapper.java 3、ZjGxbService.java 4、ZjGxbController.java 5、调用接口测试数据是否正确 6、打包放到服务器即可 前言&#xff1a;springboot整体架构 文件架构&#xff0c;主要编写框选的这几类…

【已解决】 Tomcat10.1.x使用JSTL标签库

IDEA创建Java EE项目&#xff0c;使用Spring Spring MVC MyBatis框架&#xff0c;使用maven管理依赖。项目当前的环境是&#xff1a; Tomat 10.1.28Maven 3.6.3JDK 17 项目的功能&#xff1a;读取数据库的report表中的数据&#xff0c;返回一个List集合对象reportList在JSP…

权限相关知识

1.Linux权限的概念 在说Linux权限的概念之前我来问大家一个问题&#xff0c;你们觉得什么是权限&#xff1f; 权限平时的体现呢&#xff0c;就比如不是校长的亲戚就不能逛办公室&#xff0c;没充会员的爱奇艺看不了VIP影视剧&#xff0c;没成会员的的蛋糕店拿不到会员价等等等…

uniapp如何i18n国际化

1、正常情况下项目在代码生成的时候就已经有i18n的相关依赖&#xff0c;如果没有可以自行使用如下命令下载&#xff1a; npm install vue-i18n --save 2、创建相关文件 en文件下&#xff1a; zh文件下&#xff1a; index文件下&#xff1a; 3、在main.js中注册&#xff1a…

[刷题]入门3.彩票摇奖

博客主页&#xff1a;算法歌者本篇专栏&#xff1a;[刷题]您的支持&#xff0c;是我的创作动力。 文章目录 1、题目2、基础3、思路4、结果 1、题目 链接&#xff1a;洛谷-P2550-彩票摇奖 2、基础 此题目考察数组、三重循环、自增操作的能力。 3、思路 写代码时候&#xf…

JVM垃圾回收详解(重点)

堆空间的基本结构 Java 的自动内存管理主要是针对对象内存的回收和对象内存的分配。同时&#xff0c;Java 自动内存管理最核心的功能是 堆 内存中对象的分配与回收 Java 堆是垃圾收集器管理的主要区域&#xff0c;因此也被称作 GC 堆&#xff08;Garbage Collected Heap&…

git rebase --continue解冲突操作

git rebase --continue解冲突操作 如果只是执行了 git rebase 命令&#xff0c;那么git会输出一下“错误”提示&#xff1a; There is no tracking information for the current branch. Please specify which branch you want to rebase against. See git-rebase(1) for detai…

腾讯地图GL JS点标识监听:无dragend事件的经纬度获取方案

引入腾讯地图SDK <!--腾讯地图 API--><script charset"utf-8" src"https://map.qq.com/api/gljs?librariestools&v1.exp&key***"></script>构建地图容器 <div class"layui-card"><div class"layui-car…

249: 凸包面积

解法&#xff1a; 使用Andrew算法【计算几何/凸包】安德鲁算法&#xff08;Andrews Algorithm&#xff09;详解_andrew算法求凸包-CSDN博客 排序&#xff1a; 将所有点按照x坐标进行升序排序。如果x坐标相同&#xff0c;则按照y坐标升序排序。 初始化栈&#xff1a; 使用一个栈…

基于VUE实现语音通话:边录边转发送语言消息、 播放pcm 音频

文章目录 引言I 音频协议音频格式:音频协议:II 实现协议创建ws对象初始化边录边转发送语言消息 setupPCM按下通话按钮时开始讲话,松开后停止讲话播放pcm 音频III 第三库recorderplayer调试引言 需求:电台通讯网(电台远程遥控软件-超短波)该系统通过网络、超短波终端等无线…

【Rust中的项目管理】

Rust中的项目管理 前言Package&#xff0c;Crate&#xff0c;Module &use &#xff0c;Path通过代码示例解释 Crate&#xff0c;Module &#xff0c;use&#xff0c;Path创建一个package&#xff1a;代码组织化skin.rs 中的代码struct & enum 相对路径和绝对路径引用同…

极客争锋 智连未来 TuyaOpen Framework极客创意大赛正式开启

TuyaOpen Framework极客创意大赛正式开启 可选择基于: TuyaOpen Framework 原生开源包: https://github.com/tuya/tuyaopen 支持 Ubuntu/T2/T3/T5/ESP32/ESP32C3等多款芯片TuyaOpen Arduino:https://github.com/tuya/arduino-tuyaopen支持 T2/T3/T5等多款芯片TuyaOpen LuaNode…

安装SQL server中python和R

这两个都是编程语言 R 是一种专门为统计计算和数据分析而设计的语言&#xff0c;它具有丰富的统计函数和绘图工具&#xff0c;常用于学术研究、数据分析和统计建模等领域。 Python 是一种通用型编程语言&#xff0c;具有简单易学、语法简洁、功能强大等特点。它在数据科学、机…

A029-基于Spring Boot的物流管理系统的设计与实现

&#x1f64a;作者简介&#xff1a;在校研究生&#xff0c;拥有计算机专业的研究生开发团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339; 赠送计算机毕业设计600…

理解HTTP中的Cookie与Session:机制、安全性与报头响应

文章目录 1. HTTP Cookie1.1. HTTP Cookie 工作流程1.2. Cookie 分类1.3. 安全性主要用途 2. Set-Cookie 报头2.1. Set-Cookie 格式2.2. 生命周期 3. HTTP Session3.1. 工作流程3.2. 安全性3.3. 超时 与 失效3.4. 用途 1. HTTP Cookie HTTP Cookie&#xff08;也称为 Web Cook…