声明式事务的属性~

简介: 声明式事务的属性~

说明:本篇文章的代码是基于上篇文章之上的,如有需要,请移至这篇文章

声明式事务的属性:只读,超时,回滚策略:

事务属性:只读

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

设置方法:

@Transactional(readOnly = true)

大家注意看该属性值的默认值为false,那么也就是说,只有在事务中的操作都为查询操作时,才需要将其设置为只读

注意:只有当事务中的操作都为查询操作时,才能设置事务的只读操作,而如果事务中出现了其他的操作,我们依然将其设置为只读,就会报错

举例:当我们在包含除查询以外的其他操作的事务中,将该事务设置为只读:

报错如下所示:

事务属性:超时

事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源,而长时间占用资源,大概率是因为程序运行出现了问题(可能是java程序或者mysql数据库或网络连接等等)


此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常程序可以执行


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

设置方法:

//将当前的事务执行时间设置为3秒钟,如果超过三秒钟,则直接抛出异常
@Transactional(timeout = 3)

举例:

报错如下:事务超时异常

事务属性:回滚策略

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

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

//通常情况下,第一二种我们是不进行设置的,因为所有的运行时异常都会被回滚
rollbackFor属性:需要设置一个Class类型的对象
rollbackForClassName属性:需要设置一个字符串类型的全类名
noRollbackFor属性:需要设置一个Class类型的对象
rollbackFor属性:需要设置一个字符串类型的全类名

举例:

更新表中的数据,使其余额大于图书价格,如下所示:

第二步:在购买图书的方法末,手动添加运行时异常,如下所示:

报错如下:报错内容似乎并没有关于图书购买失败的信息,只抛出了我们手动设置的运行时异常,那么这是代表图书购买成功了吗?

在数据库中进行查询,发现余额和图书的库存量都没有发生改变,原因是:声明式事务默认只针对运行时异常回滚,我们并没有设置其值,那么就是处于默认情况下

但上述的数学运算异常好像和我们要处理的业务并没有关系,由此我们可以通过回滚策略进行处理

方法1:

如下所示:

@Transactional(noRollbackFor = {ArithmeticException.class})

noRollbackFor的类型为数组类型[如下所示],因此当其值为多个时,必须使用{}包含,如果其值只有一个,则"{}"可省略

再次运行结果如下所示:

好像和上面的结果并没有什么不同,我们再去数据库进行数据的查询,如下所示,图书购买成功,原因是:我们通过@Transactional(noRollbackFor = {ArithmeticException.class})设置当运行时异常为数学运算错误时,不进行回滚操作,因此事务没有回滚

方法2:
@Transactional(noRollbackForClassName = "java.lang.ArithmeticException")

由于noRollbackForClassName的类型为字符串,因此,它的值为该异常的全类名

这种方法的产生效果和第一种完全相同,这里就不演示了

注意:在进行测试时,必须要满足图书是能够被成功购买的条件,因为如果图书都无法被成功购买,那么代码将无法运行到方法末的数学运算

声明式事务的属性之隔离级别:

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

隔离级别一共有四种:

读未提交:READ UNCOMMITTED
允许Transaction01读取Transaction02未提交的修改
读已提交:READ COMMITTED
要求Transaction01只能读取Transaction02已提交的修改
可重复读:REPEATABLE READ
确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其他事务对这个字段进行更新
串行化:SERIALIZABLE
确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其他事务对这个表进行添加,更新,删除操作,可以避免任何并发问题,但性能十分低下

各个隔离级别解决并发的能力:

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

设置属性的隔离级别:

方法:

//一般将其设置为默认值
@Transactional(isolation = Isolation.DEFAULT)

打开Isolation的源码,如下所示,Isolation是一个枚举类型

声明式事务的属性之传播行为:

什么是时候之间的传播行为呢?举例来说就是假设现在有两个方法A和B,这两个方法都加上了@Transactional注解,此时这两个方法都有事务的功能,假设现在A事务方法去调用B事务方法,但由于AB两个方法都有事务的功能,因此当A方法调用B事务方法的这个过程中,就会将A方法中的事务传递给B方法,这就造成了B方法现在既有自己本身的事务功能,还有A事务的事务功能,它在实行功能的时候,既可以实行自己本身的额,也可以实行A传递给它的


下面我们就通过具体的实例来演示该功能:


创建实现买单功能的接口:

package spring_txAnnotation.Service;
public interface CheckOutService {
    void Checkout(Integer userId, Integer[] bookId);
}

创建实现买单功能的接口的实现类:

package spring_txAnnotation.Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import spring_txAnnotation.Controller.BookController;
@Service
public class CheckOutServiceImpl implements CheckOutService{
    @Autowired
    private ServiceBook serviceBook;
    @Autowired
    private CheckOutService checkOutService;
    @Autowired
    private BookController bookController;
    public void buyBook(Integer userId,Integer bookId){
        serviceBook.buyBook(userId,bookId);
    }
    @Transactional
    @Override
    public void Checkout(Integer userId, Integer[] bookId) {//结账方法
       for(Integer bookIds:bookId){
           serviceBook.buyBook(userId,bookIds);
       }
    }
}

将新功能的实现代码加入Controller层:

  @Autowired
    private CheckOutService checkOutService;
    public void Checkout(Integer userid, Integer[] integers) {
        checkOutService.Checkout(userid,integers);
    }

测试类如下所示:

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;
import spring_txAnnotation.Controller.BookController;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:TXannotation.xml")
public class Test1 {
    @Autowired
    private BookController bookController;
    @Test
    public void testBuyBooks(){
        bookController.Checkout(1,new Integer[] {1,2});
    }
}

我们为其新添加了一个事务之后,就导致当前的组件含有两个事务功能,一个是买书操作,一个是结账功能,如下所示:

在运行之前,我们先对数据库中的数据进行查询:

如下所示:

当前用户的余额为100元,只能满足购买一本书的条件,因此,如果执行的是结账操作,那么意味着将购买两本书,显然当前的用户余额是无法满足的,那么就会出现两本书都无法被购买的情况,下面我们运行该程序

报错如下所示:

此时我们再次访问数据库中的数据,如下所示:

用户余额和图书库存量都没有发生任何的改变,由此可说明当前执行的是结账的事务功能,因为只要有一本书无法购买,会导致整个事务回滚到最初的状态

那么如果我们想设置使之执行的是本身的买书事务功能呢?

方法:通过@Transactional中的propagation属性设置事务传播行为

打开该属性的源码,如下所示,我们发现该属性的默认值是Propagation.REQUIRED,表示如果当前线程上有已经开启的事务可用,那么就在这个事务中运行

经过观察,购买图书的方法buyBook0在checkout0中被调用[如下所示],checkout0上有事务注解,因此在此事务中执行。所购买的两本图书的价格为80和50,而用户的余额为100,因此在购买第二本图书时余额不足失败,导致整个checkout()回滚,即只要有一本书买不了,就都买不了


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


设置完毕之后,再次运行,数据库中的数据如下所示:



相关文章
|
6月前
|
Java Spring 容器
Spring5源码(42)-@Transactional注解的声明式事物事物标签提取
Spring5源码(42)-@Transactional注解的声明式事物事物标签提取
41 0
|
6月前
Spring5源码(45)-@Transactional声明式事物(三)事物创建
Spring5源码(45)-@Transactional声明式事物(三)事物创建
70 0
|
数据库
通过基于注解的声明式事务实现事务功能~2
通过基于注解的声明式事务实现事务功能~
|
6月前
|
Java Spring 容器
Spring注解开发定义bean及纯注解开发模式
Spring注解开发定义bean及纯注解开发模式
58 0
|
6月前
|
XML Java 关系型数据库
注解驱动事务:Spring中基于注解的事务属性配置详解
注解驱动事务:Spring中基于注解的事务属性配置详解
109 0
注解驱动事务:Spring中基于注解的事务属性配置详解
|
6月前
|
XML 运维 Java
spring事务(3)基于XML的声明式事务
spring事务(3)基于XML的声明式事务
36 0
|
6月前
|
Java 数据库 Spring
Spring5源码(48)-@Transactional声明式事物(六)嵌套事物处理
Spring5源码(48)-@Transactional声明式事物(六)嵌套事物处理
56 0
|
6月前
|
Java 数据库 Spring
Spring5源码(40)-基于tx标签和基于@Transactional注解的声明式事物介绍
Spring5源码(40)-基于tx标签和基于@Transactional注解的声明式事物介绍
56 0
|
XML Java 关系型数据库
通过基于注解的声明式事务实现事务功能~1
通过基于注解的声明式事务实现事务功能~
通过基于注解的声明式事务实现事务功能~1
|
Java 数据库连接 数据库
Spring 事务【Spring事务的定义与基本实现】
Spring 事务【Spring事务的定义与基本实现】
Spring 事务【Spring事务的定义与基本实现】