通过基于注解的声明式事务实现事务功能~2

简介: 通过基于注解的声明式事务实现事务功能~

事务属性:回滚策略

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

可以通过@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月前
|
XML SQL Java
【Spring事务】声明式事务 使用详解
【Spring事务】声明式事务 使用详解
87 0
|
6月前
|
SQL Java 数据库连接
spring声明式事务 @Transactional 不回滚的多种情况以及解决方案
spring声明式事务 @Transactional 不回滚的多种情况以及解决方案
144 0
|
XML Java 关系型数据库
通过基于注解的声明式事务实现事务功能~1
通过基于注解的声明式事务实现事务功能~
通过基于注解的声明式事务实现事务功能~1
|
XML Java 数据库
Spring框架中如何处理事务管理
Spring框架中如何处理事务管理
156 0
|
Java 数据库连接 数据库
Spring 事务【Spring事务的定义与基本实现】
Spring 事务【Spring事务的定义与基本实现】
Spring 事务【Spring事务的定义与基本实现】
|
SQL Java 数据库
事务注解:@Transtation
事务注解:@Transtation
|
SQL Oracle Java
Spring——Spring中的事务、使用注解(@Transactional)控制事务、使用AspectJ框架控制事务 (上)
Spring——Spring中的事务、使用注解(@Transactional)控制事务、使用AspectJ框架控制事务 (上)
Spring——Spring中的事务、使用注解(@Transactional)控制事务、使用AspectJ框架控制事务 (上)
|
Java 数据库连接 数据库
Spring——Spring中的事务、使用注解(@Transactional)控制事务、使用AspectJ框架控制事务 (下)
Spring——Spring中的事务、使用注解(@Transactional)控制事务、使用AspectJ框架控制事务 (下)
Spring——Spring中的事务、使用注解(@Transactional)控制事务、使用AspectJ框架控制事务 (下)
|
XML Java 关系型数据库
Spring的事务操作一站式学习【事务的概念、注解声明式事务管理、声明式事务管理参数配置、XML声明式事务管理、完全注解声明式事务管理】(超详细)
Spring的事务操作一站式学习【事务的概念、注解声明式事务管理、声明式事务管理参数配置、XML声明式事务管理、完全注解声明式事务管理】(超详细)
Spring的事务操作一站式学习【事务的概念、注解声明式事务管理、声明式事务管理参数配置、XML声明式事务管理、完全注解声明式事务管理】(超详细)
|
SQL druid Java
Spring @Transactional 注解是如何执行事务的?
相信小伙伴一定用过 @Transactional 注解,那 @Transactional 背后的秘密又知道多少呢? Spring 是如何开启事务的?又是如何进行提交事务和关闭事务的呢?
271 0