Spring 全家桶之 Spring Framework 5.3(七)- 声明式事务(三)

简介: Spring 全家桶之 Spring Framework 5.3(七)- 声明式事务

isolation

数据库事务并发问题

假设现在有两个事务,t1和t2并发执行

事务隔离级别

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

①读未提交:READ UNCOMMITTED

  • 允许t1读取t2未提交的修改,这就导致了脏读。
  • t2提交,t1再次读取,读取到的数据是修改过的数据,与上一次读取到的数据不一致,这就导致了不可重复读
  • t2向表中插入一些新的book信息,t1查询所有,可以查到新增加的数据,这就导致了幻读 ②读已提交:READ COMMITTED
  • t1只能读取t2已提交的修改,避免了脏读 ③可重复读:REPEATABLE READ
  • 确保t1可以多次从一个字段中读取到相同的值,即t1执行期间禁止其它事务对这个字段进行更新。 ④串行化:SERIALIZABLE
  • 确保t1可以多次从一个表中读取到相同的行,在t1执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。

mysql查询隔离级别的命令

修改MySQL隔离级别

SET [SESSION(当前会话) | GLOBAL(全局)] TRANSACTION ISOLATION LEVEL

{
READ UNCOMMITTED | 
READ COMMITTED | 
REPEATABLE READ | 
SERIALIZABLE
}

如:设置当前会话的事务隔离级别为读未提交

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

查询MySQL的隔离级别

SELECT @@global.tx_isolation; //查询全局隔离级别

SELECT @@session.tx_isolation;//查询当前会话隔离级别

SELECT @@tx_isolation;//同上

事务操作

开启事务  start transaction;

提交事务  commit;

回滚事务  rollback;

读未提交READ_UNCOMMITTED

BookService中增加getPrice方法,设置isolation属性为READ_UNCOMMITTED

@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public int getPrice(String isbn){
    return bookDao.getPrice(isbn);
}
复制代码

BookServiceTest中增加测试方法

// 读未提交测试
@Test
public void getPrice() {
    int price = bookService.getPrice("ISBN-001");
    System.out.println("价格:" + price);
}
复制代码

首先执行查询图书价格测试,查询到的图书价格为100

39ffd7b264444ea8aa6dacb48f33eaa3_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png

然后在命令行中开启一个事务,修改图书价格,不提交

8f7fd07e6a284e4099b0796a38ac2e76_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png

再次执行测试

aacab954f1e642fbb4381046a86aa2cf_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png

控制台输出价格为200,读取到了命令行中未提交的脏数据

读已提交READ_COMMITTED

首先恢复price为100,将代码中隔离级别修改为READ_COMMITTED,此时新打开一个命令行窗口,执行修改图书价格为200的SQL,再次执行getPrice方法的测试

c0280b21ca5e49ebb3f515bf64ce8eca_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png

44dc17a956244c688c72d0503fe79e40_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png

在未提交的情况下,READ_COMMITTED隔离级别读取到的数据仍然是修改前的数据,此时在命令行窗口执行commit提交修改命令,再次执行测试

image.png

读取到了提交修改后的值,避免了脏读,但是前后两次读取到的数据不一致导致了不可重复读

可重复读REPEATABLE-READ

任何时候读取都是一样的,打开两个命令行窗口,首先在第一个命令行中开启事务并设置隔离级别为REPEATABLE-READ,并查询一次price,结果为100;接着在第二个命令行中更新并提交price,在第一个命令行中再次查询price,结果仍然为100;在第二个命令行中执行删除并提交的操作,在第一个命令行中再次查询price,结果仍然为100;这就是可重复读,在一个会话SESSION中,读取到的数据自始至终都是一样的,避免了脏读和不可重复读。

image.png

plus:并发修改数据时会出现排队的现象,只有等待另一个的修改commit之后,才能继续修改。这种现象与隔离级别无关。

image.png

有事务的业务逻辑,容器中保存的是这个业务逻辑的代理对象,只有代理对象才可以执行事务

@Test
public void getClazz(){
    System.out.println(bookService.getClass());
}
复制代码

image.png

propagation

事务传播行为,如果有多个事务嵌套运行,子事务是否要和上层事务或者已存在的事务共享同一个事务

  • REQUIRED: 支持当前事务,没有则新建
  • REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起
  • SUPPORTS:如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中
  • NOT_SUPPORTS:当前的方法不应该运行在事务中,如果有运行的事务,将它挂起
  • MANDATORY:当前的方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常
  • NEVER:当前方法不应该运行在事务中,如果运行在事务中就抛出异常
  • NESTED:如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则就启动一个新的事务,并在它自己的事务内运行

以上属性,只有REQUIRED和REQUIRES_NEW是最常用的。

BookDao中新增方法,更新图书价格

public void updatePrice(String isbn, int price){
    String updatePriceSql = "UPDATE book SET price=? WHERE isbn=?";
    jdbcTemplate.update(updatePriceSql,price,isbn);
}
复制代码

BookService中调用BookDao的updatePrice方法,并增加事务@Transactional

@Transactional
public void updatePrice(String isbn,int price){
    bookDao.updatePrice(isbn, price);
}
复制代码

新增一个ComplexService,注入BookService,新增一个collaborateTransaction方法,该方法中调用了BookService的checkout方法和updatePrice方法,并增加@Transactional注解,这样就形成了collaborateTransaction一个大的事务里面嵌套了checkout和updatePrice两个小的事务,当其中collaborateTransaction发生异常或者checkout或者updatePrice发生异常,其他事务是否会回滚呢?

@Service
public class ComplexService {
    @Resource
    private BookService bookService;
    // 综合事务
    @Transactional
    public void collaborateTransaction(String username,String isbn, int price){
        // 事务1,事务2失败事务1是否需要回滚?可设置是否回滚,这就是事务传播行为,
        // 是否和上层事务共享一个事务
        bookService.checkout(username,isbn);
        // 事务2
        bookService.updatePrice(isbn,price);
    }
}
复制代码

REQUIRED

给BookService的checkout方法和updatePrice设置传播行为,即给@Transactional注解增加属性propagation = Propagation.REQUIRED,checkout的@Transactional注解中其他演示过的属性可以删除,REQUIRED的意思是该方法需要事务,如果存在事务就使用已存在的事务,如果没有就新建一个事务

新增一个测试类ComplexServiceTest, 继承BookServiceTest

public class ComplexServiceTest extends BookServiceTest{
    @Resource
    private ComplexService complexService;
    @Test
    public void collaborateTransaction() {
        complexService.collaborateTransaction("Stark","ISBN-002",200);
    }
}
复制代码

恢复默认数据,所有book的price改为100,stock都改为1000,并且在updatePrice方法中增加异常代码

// 异常代码
System.out.println(10/0);
复制代码

执行测试,查看数据库,price、stock、balance数据都没有变化,可以确定发生异常后chekcout和update都进行了回滚

REQUIRES_NEW

修改checkout的事务属性为Propagation.REQUIRES_NEW,即创建一个新事务,不与其他方法共享事务,发生异常时其他事务不会回滚,再次执行测试。

image.png

可以发现price价格不变,updatePrice发生了回滚

image.png

stock库存数量减少,checkout正常执行并没有进行回滚

将collaborateTransaction方法中的checkout方法和updatePrice方法都改为REQIURES_NEW,并且在collaborateTransaction中增加异常,注释updatePrice方法中的异常,恢复初始数据后执行测试

image.png

price发生变化,updatePrice方法没有回滚

image.png

stock库存数量减少,checkout方法也没有回滚

这是因为两个事务是新的事务,与上层方法的事务不属于同一个事务,所有上层方法出现异常并不会影响这两个方法

plus:与上层方法共享事务时,该事物本身设置的属性都失效,以上层事务设置的属性为准。

五、基于XML的声明式事务

基于注解的声明式事务配置步骤

  1. Spring中提供事务管理器(事务切面),配置事务管理器
  2. 开启基于注解的声明式事务,依赖tx名称空间
  3. 需要事务的方法上添加注解

基于XML配置的声明式事务,依赖tx和aop名称空间

<!--基于XML配置的声明式事务-->
<aop:config>
    <!--切面-->
    <aop:pointcut id="txPoint" expression="execution(* com.citi.service.*.*(..))"/>
    <!--增强 advice-ref:指向事务管理器配置-->
    <aop:advisor advice-ref="myAdvice" pointcut-ref="txPoint"></aop:advisor>
</aop:config>
<!--配置事务管理器-->
<tx:advice id="myAdvice" transaction-manager="dataSourceTransactionManager">
    <tx:attributes>
        <!--指明哪些方法是事务方法-->
        <!--切入点表达式只是说事务管理器要切入这些方法,哪些方法加事务使用tx:method指定-->
        <tx:method name="checkout" propagation="REQUIRED" timeout="-1"/>
        <tx:method name="get*" read-only="true"></tx:method>
    </tx:attributes>
</tx:advice>
复制代码

将所有方法上的@Transactional注解全部注释 执行BookServiceTest中的testCheckoutSuccess方法

@Test
public void testCheckoutSuccess() throws FileNotFoundException {
    bookService.checkout("Stark","ISBN-002");
    System.out.println("Checkout Success!");
}
复制代码

执行后库存和余额都能正常扣除

在BookService的checkout方法增加异常代码

// 测试基于XML配置的声明式事务时的异常代码
int i = 10 / 0;
复制代码

再次执行测试。查看数据库可以确定余额和库存发生了回滚,数据不变

最后基于注解的声明式事务配置和基于XML的声明式事务如何选择?

重要的事务使用配置或者当事务非常非常多的时候,不重要的事务使用注解


相关文章
|
3天前
|
Java 数据库 开发者
|
3天前
|
Java 开发者 Spring
Spring Framework 中的 @Autowired 注解:概念与使用方法
【4月更文挑战第20天】在Spring Framework中,@Autowired 注解是实现依赖注入(Dependency Injection, DI)的一种非常强大的工具。通过使用 @Autowired,开发者可以减少代码中的引用绑定,提高模块间的解耦能力
37 6
|
3天前
|
Java 关系型数据库 MySQL
【JavaEE】Spring事务-@Transactional参数介绍-事务的隔离级别以及传播机制
【JavaEE】Spring事务-@Transactional参数介绍-事务的隔离级别以及传播机制
9 0
|
3天前
|
消息中间件 Java 关系型数据库
【JavaEE】Spring事务-事务的基本介绍-事务的实现-@Transactional基本介绍和使用
【JavaEE】Spring事务-事务的基本介绍-事务的实现-@Transactional基本介绍和使用
8 0
|
3天前
|
SQL Java 关系型数据库
Spring 事务
Spring 事务
12 1
|
3天前
|
Java 数据库连接 数据库
Spring事务简介,事务角色,事务属性
Spring事务简介,事务角色,事务属性
18 2
|
3天前
|
Java 数据库连接 数据库
16:事务-Java Spring
16:事务-Java Spring
29 5
|
3天前
|
消息中间件 Java 关系型数据库
Spring事务与分布式事务
这篇文档介绍了事务的概念和数据库事务的ACID特性:原子性、一致性、隔离性和持久性。在并发环境下,事务可能出现更新丢失、脏读和不可重复读等问题,这些问题通过设置事务隔离级别(如读未提交、读已提交、可重复读和序列化)来解决。Spring事务传播行为有七种模式,影响嵌套事务的执行方式。`@Transactional`注解用于管理事务,其属性包括传播行为、隔离级别、超时和只读等。最后提到了分布式事务,分为跨库和跨服务两种情况,跨服务的分布式事务通常通过最终一致性策略,如消息队列实现。
|
3天前
|
监控 Java 测试技术
Spring Boot与事务钩子函数:概念与实战
【4月更文挑战第29天】在复杂的业务逻辑中,事务管理是确保数据一致性和完整性的关键。Spring Boot提供了强大的事务管理机制,其中事务钩子函数(Transaction Hooks)允许开发者在事务的不同阶段插入自定义逻辑。本篇博客将详细探讨事务钩子函数的概念及其在Spring Boot中的应用。
37 1
|
3天前
|
XML Java 数据库连接
精妙绝伦:玩转Spring事务编程的艺术
【4月更文挑战第20天】
34 0