当前位置 : 主页 > 编程语言 > java >

Spring事务管理原理及方法详解

来源:互联网 收集:自由互联 发布时间:2021-05-10
这篇文章主要介绍了Spring事务管理原理及方法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 事务,在日常开发或者面

这篇文章主要介绍了Spring事务管理原理及方法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

事务,在日常开发或者面试中都必定会涉及到。开发工作中,结合数据库开发理解就是:一组dml要么全部成功执行提交,要么因为某一个操作异常,撤销之前所做的成功的操作,整体执行失败。再简单点的一句话:生死与共。

由此,可以看出,事务的必要性:在开发工作中,保证操作数据的安全性。事务的控制也就是保证数据的访问安全性。

一、事务的四大特性

A:原子性(atomicity),对数据的修改,要么全部成功执行,要么全部不执行。

C:一致性(consistency),一旦事务完成,系统必须保证数据职员是满足业务状态的一种一致状态中。很难懂的解释,跟原子性很像。一个事务在操作过程中,数据可能会产生很多中间态,一致性保证中间态对其他事务不可见,因为这些中间态,与事务的开始和结束的状态是不一致的。也就是从一种正确的状态到另一种正确的状态。

I:隔离性(isolation),事务之间的执行应不相互影响,也即事务执行的独立。

D:持久性(durability),事务一旦提交,则对数据库的修改是永久性的。

二、事务的隔离级别

并发环境下,事务可能会存在若干问题:脏读、幻读、不可重复读、第一类更新丢失、第二类更新丢失。

类型   说明 举例 脏读 A事务读取到了B事务未提交的数据  A开启事务=>B开启事务,读取账户1000块,取走100块=>A读取账户金额,读取到900=>B回滚事务。此时A读取的余额数据是无效的 幻读  一个事务里面的操作发现了未被操作的数据

 A开启事务,修改某些数据状态=>B开启事务,执行新增数据并提交=>A事务提交,会出现一条未被修改的数据。

幻读发生的前提是并发事务中发生了新增或者删除动作。

不可重复读  一个事务中,先后两次读取数据,读到的结果不一致

 A开启事务,读取账户1000块=>B开启事务,读取账户1000块,取出100块并提交事务=>A再读取账户余额,余额900块。

一个事务范围内的两次同样的查询,却返回了两次不同的数据,这就是不可重复读

第一类更新丢失 A事务撤销,把已经提交的B事务的更新的数据覆盖 A开启事务,读取账户1000块=>B开启事务,读取账户1000块,然后增加100块,提交事务,账户变为1100=>A撤销回滚事务,账户成1000块 第二类更新丢失 A事务提交,把已经提交的B事务的更新的数据覆盖 A开启事务,读取账户1000块=>B开启事务,读取账户1000块,然后增加100块,提交事务,账户变为1100=>A增加100块,提交事务,账户变为1100。

针对并发环境下可能出现的事务问题,于是就出现了隔离级别的解决方案,由低到高依次是:读未提交(Read uncommitted)、读已提交(Read committed)、可重复读(Repeatable read)、串行序列化(serializable)。下表展示出不同的隔离级别,对于脏读、幻读、不可重复读是否会出现。

类型 脏读 不可重复读 幻读 说明 Read uncommitted 会 会  会   Read committed 不会 会 会   Repeatable read 不会 不会 会 mysql的默认隔离级别 serializable 不会 不会 不会 最严格的隔离级别,将事务串行化执行,性能低。

mysql中查询当前隔离级别:select @@tx_isolation;

三、spring事务支持的隔离级别和传播特性

spring中定义了五种隔离界别和七种传播行为(可以在org.springframework.transaction.TransactionDefinition类中看到详细的解释)

1、spring支持的隔离级别

ISOLATION_DEFAULT:默认级别。一般是使用的是数据库本身的隔离级别(mysql - Repeatable read 、oracle - Read committed)

余下ISOLATION_READ_UNCOMMITTED、ISOLATION_READ_COMMITTED、ISOLATION_REPEATABLE_READ、ISOLATION_SERIALIZABLE分别对应上述数据库隔离级别配置。

2、传播特性

事务的传播特性就是在多个事务方法互相调用的时候,事务该如何在方法之间使用传播:

  •     PROPAGATION_REQUIRED:如果当前有事务,则加入该事务中。如果没有,则新建事务。
  •     PROPAGATION_SUPPORTS:如果当前有事务,则加入该事务中。如果没有,则以非事务状态运行。
  •     PROPAGATION_MANDATORY:如果当前有事务,则加入该事务中。如果没有,则抛出无事务异常
  •     PROPAGATION_REQUIRES_NEW:新建事务。如果当前存在事务,则挂起该事务。
  •     PROPAGATION_NOT_SUPPORTED:非事务状态运行。如果当前存在事务,则挂起该事务。
  •     PROPAGATION_NEVER:非事务状态运行。如果当前存在事务,则抛出异常。
  •     PROPAGATION_NESTED:如果当前有事务,则嵌套事务(父子事务)执行。如果没有,类似PROPAGATION_REQUIRED处理。

  spring默认的传播行为是PROPAGATION_REQUIRED,一般适用于绝大多数的开发工作。

3、事务的超时属性

事务在超过预定时间内还未完成操作,则自动回滚事务。TransactionDefinition 中以int值来表示超时时间,单位是秒,提供的默认值是TIMEOUT_DEFAULT = -1,即永不超时,一直等待操作完成

四、spring事务配置方法

spring并不具体直接的管理事务,而是提供了一个接口org.springframework.transaction.PlatformTransactionManager,该接口中主要定义了三个方法:getTransaction(获取事务)、commit(提交)和rollback(回滚)。

根据不同的持久化策略,spring提供了不同的实现,比如jdbc -

org.springframework.jdbc.datasource.DataSourceTransactionManager、hibernate - org.springframework.orm.hibernate5.HibernateTransactionManager等,在其他的实现可以通过源码去查询。

spring事务使用,可以分为编程式事务和声明式事务。编程式事务每次业务使用都得书写获取事务、设置事务隔离级别和传播特性、提交或回滚事务,代码的重复太高,费时费力,且如果代码的功能性复杂时候,使用编程式变得更加痛苦。而声明式的事务,属于无侵入式的,不会影响主业务流程,且编写上非常简单。所以目前开发工作中,更多的是使用声明式事务。

1、编程式事务

//to do。后续补充

2、声明式事务

声明式事务分为两种:基于aop的织入和@Transactional的注解。

2.1、基于aop的事务织入

之后在servcie中定义方法,最好的就是tx:method中定义的格式开头,就会执行特定的事务。

2.2、注解事务

注解事务第一部分的数据源和事务管理器配置同上,配置文件中需要修改的是开启注解配置:

然后在service编程中,加注解@Transactional即可(建议只在service实现类中加),如下是配置样例,其中的属性可以按需设置:

@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ,timeout = 100, readOnly = false, rollbackFor = {},noRollbackFor = {})

注意点:

事务的异常回滚只检查RuntimeException的异常,checked exception(如ClassNotFoundException、FileNotFoundException等)不会滚,捕获异常不抛出也不会回滚。

五、spring和springmvc父子容器

现在开发工作中,一般大多数都使用的spring和springmvc构建,这里,spring容器和springmvc容器就构成了父子容器的关系。父容器spring是发现不了子容器springmvc中的bean的,而子容器可以发现父容器中注册的bean。由此,实际开发工作中,不注意的话,往往会产生一些意想不到的问题。

首先,通常我们配置spring配置文件applicationContext.xml的时候,会配置如下的扫描:

<context:component-scan base-package="com.cfang" />

这个配置会扫描指定包下面的所有@Component类型注解,包括@Controller,@Service,@Respository,并将扫描到的bean注册到spring容器中。

一般,spring配置文件中,还会出现下面的配置,作用是扫描@Required、@Autowired、 @PostConstruct、@PersistenceContext、@Resource、@PreDestroy等注解。理论上,此配置为可选配置,因为上面的扫描配置会默认打开。

<context:annotation-config/>

接下来配置springmvc的配置文件spring-mvc.xml,配置扫描注解@RequestMapping、@RequestBody、@ResponseBody等,同时,该配置默认加载很多参数绑定方法 。

<mvc:annotation-driven />

上面一句话,相当于:

<!--配置注解控制器映射器,它是SpringMVC中用来将Request请求URL到映射到具体Controller-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
<!--配置注解控制器映射器,它是SpringMVC中用来将具体请求映射到具体方法-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>

上面聊完基本配置,以下梳理下,来了解可能产生的容器冲突,对于事务管理的影响。

首先,有两个基本的容器:spring和springmvc,配置文件分别为applicationContext.xml和spring-mvc.xml

1、applicationContext.xml中配置<context:component-scan base-package="com.cfang" />,扫描指定包下的所有bean,并自动注册到spring容器中

2、spring-mvc.xml配置<mvc:annotation-driven />,扫描相关的springmvc的注解

3、为了保证springmvc的正常跳转,通常我们还得在spring-mvc.xml文件中配置包扫描<context:component-scan base-package="com.cfang" />。

按照以上配置信息,就会产生事务失效。原因就在于:

Spring容器优先加载由ServletContextListener(对应applicationContext.xml)产生的父容器,

而SpringMVC(对应spring-mvc.xml)产生的是子容器。子容器Controller进行扫描装配时装配的@Service注解的实例是没有经过事务加强处理,即没有事务处理能力的Service,

而父容器进行初始化的Service是保证事务的增强处理能力的。如果不在子容器中将Service排除(exclude)掉,此时得到的将是无事务处理能力的Service。

解决办法按照官方建议的来配置,各自负责一部分加载:

spring扫描:

<context:component-scan base-package="com.cfang.WeChat" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
  </context:component-scan>

springmvc扫描:

<context:component-scan base-package="com.cfang.WeChat" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
  </context:component-scan>

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持易盾网络。

网友评论