zoukankan      html  css  js  c++  java
  • spring事务管理

    1、spring4种事务特性,5种隔离级别,7种传播行为,事务失效原因分析

    1.1spring4种事务特性

    事务:逻辑上一组操作,这些操作要么一起成功,要么一起失败。

    特性:(ACID)

    原子性(atomicity): 事务不可分割,要么全执行,要么不执行。(

    一致性(consistency) 事务执行前后数据的完整性保持一致(如银行转帐,A转帐给B,必须保证A的钱一定转给B,一定不会出现A的钱转了但B没收到,否则数据库的数据就处于不一致(不正确)的状态。)

    隔离性(isolation) : 一个事务执行种不应该受到其他事务干扰(在一个事务内部的操作对其他事务是不产生影响,这需要事务隔离级别来指定隔离性;)

    持久性(durability):事务一旦结束,数据就持久到数据库。

    1.2事务分类

    1、数据库分为本地事务和全局事务(即分布式事务)

    本地事务:独立的一个数据库,能保证在该数据库上操作的ACID.

    分布式事务(全局事务): 多个数据库源的事务(由每个数据库的本地事务组成),分布式事务能保证这些本地事务的所有操作的ACID,使事务可跨越多数据库。

    2、java事务类型分为jdbc事务和jta事务

    Jdbc事务:即本地事务,通过connection对象控制管理

    Jta事务:(java transaction api)java事务api,提供了事务管理的接口,比jdbc更强大,支持分布式事务。

     

    3、是否通过编程分别为声明式事务和编程式事务

    声明式事务:通过xml配置或注解实现

    编程式事务:通过编程代码在业务逻辑需要时自行实现。

    小结:

    事务分类:本地事务(对应jdbc事务),全局事务(对应jta事务),实现方式声明式或编程式

    1.3事务基本原理

    Spring事务本质是数据库对事务的支持(没有数据库事务支持,spring无法提供事务功能)。单纯jdbc操作数据库使用事务步骤:

    获取连接-->开启事务-->执行crud-->提交事务-->关闭连接

    spring事务管理帮我们做了开启事务-->执行crud-->提交事务的工作。Spring是如何实现开启和关闭事务呢?以注解方式为例:

    1)配置文件开启注解驱动,在相关类和方法上使用@Transcation标识

    2)Spring启动时解析并生成相关bean,为这些类和方法生成代理,在代理种处理事务

    3)真正的数据库层事务提交和回滚通过binlogredo log实现。

    1.4不考虑隔离性存在的问题

    1)脏读:一个事务读到了另一个事务未提交的数据。(如果的一个事务回滚了,第二个事务就读到了脏数据)

    2)不可重复读:一个事务发读到了另一个事务已提交的update的数据,导致多次查询结果不一致(与幻读区别在于强调更新数据)

    3)幻读:一个事务读到了另一个事务已提交的insert的数据,导致多次查询结果不一致。(强调插入或删除数据)

    解决不可重复读问题,只需行锁。

    解决幻读需要锁表。

     

    1.5数据库中事务隔离级别(4种),解决读问题

    为解决上面的问题,需要设置事务隔离级别

    隔离级别

    脏读

    不可重复读

    幻读

    级别说明

    未提交读(read uncommited) 0

    最低级别事务隔离,运行另一个事务可看到这个事务未提交数据。

    三种问题均存在

    已提交读(read commited) 1

    保证一个事务提交后才能被另一个事务读取。

    可重复读(repeatable read) 2

    保证一个事务被提交后才能被另一个事务读取并避免了不可重复读。

    串行化Serializable 3

    代价最高但最可靠隔离级别,事务按顺序执行。对并发性能影响最大

    注意:

    1)数据库默认隔离级别:大多数为已提交读(read commited),oracle;少数为可重复读(repeatable read)mysql innodb

    Mysql中默认事务隔离级别是可重复读时不会锁住读取到的行。

    2)隔离级别为读提交时,写数据只锁住相应行。

    3)级别为可重复读时,如果有索引(包括主键索引)的时候,以索引列为条件更新数据,会存在间隙锁间隙锁、行锁、下一键锁的问题,从而锁住一些行;如果没有索引,更新数据时会锁住整张表。

    4)级别为串行化时,读写数据都会锁表。

    一般选择数据库隔离级别设为已提交读(read commited),避免脏读且有较好并发性能。不可重复读和幻读并发问题可使用悲观锁或乐观锁控制。

    1.6 spring中事务隔离级别(5种)

    隔离级别

    说明

    ISOLATION_DEFAULT

    PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与 JDBC 的隔离级别相对应。

    ISOLATION_READ_UNCOMMITTED

    未提交读,最低级别

    ISOLATION_READ_COMMITTED

    已提交读

    ISOLATION_REPEATABLE_READ

    可重复读

    ISOLATION_SERIALIZABLE

    串行化

    1.7 spring中事务传播行为(7种)

    Spring事务传播属性即定义多个事务同时存在时,如何处理这些事务的行为,这些属性在TransactionDefinition中定义。7种传播属性如下

    (假设外层事务 Service A Method A() 调用 内层Service B Method B()

    传播行为

    说明

    场景说明

    PROPAGATION_REQUIRED

    Spring默认传播行为。

    支持当前事务,如果当前没有事务则新建一个事务

    如果ServiceB.methodB() 的事务级别定义为 PROPAGATION_REQUIRED

    如果执行 ServiceA.methodA() spring创建事务,ServiceB.methodB() 已运行在 ServiceA.methodA() 的事务内部,就不再起新的事务。

    假如 ServiceB.methodB() 运行的时候发现自己没有在事务中,就会为自己分配一个事务。

    这样,在 ServiceA.methodA() 或者在 ServiceB.methodB() 内的任何地方出现异常,事务都会被回滚。

    PROPAGATION_REQUIRES_NEW

    新建事务,如果当前存在事务,把当前事务挂起。新建的事务将和被挂起的事务没有任何关系,是两个独立的事务,外层事务失败回滚之后,不能回滚内层事务执行的结果,内层事务失败抛出异常,外层事务捕获,也可以不处理回滚操作

    比如我们设计 ServiceA.methodA() 的事务级别为 PROPAGATION_REQUIREDServiceB.methodB() 的事务级别为 PROPAGATION_REQUIRES_NEW

    当执行到 ServiceB.methodB() 的时候,ServiceA.methodA() 所在的事务就会挂起,ServiceB.methodB() 会起一个新的事务,等待 ServiceB.methodB() 的事务完成以后,它才继续执行。与 PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。因为 ServiceB.methodB() 是新起一个事务,那么就是存在两个不同的事务。如果 ServiceB.methodB() 已经提交,那么 ServiceA.methodA() 失败回滚,ServiceB.methodB() 是不会回滚的。如果 ServiceB.methodB() 失败回滚,如果他抛出的异常被 ServiceA.methodA() 捕获,ServiceA.methodA() 事务仍然可能提交(主要看B抛出的异常是不是A会回滚的异常)

    PROPAGATION_SUPPORTS

    支持当前事务,如果当前没有事务,就以非事务方式执行。

    假设ServiceB.methodB() 的事务级别为 PROPAGATION_SUPPORTS,那么当执行到ServiceB.methodB()时,如果发现ServiceA.methodA()已经开启了一个事务,则加入当前的事务,如果发现ServiceA.methodA()没有开启事务,则自己也不开启事务。这种时候,内部方法的事务性完全依赖于最外层的事务。

    PROPAGATION_NESTED

    如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。

     ServiceB.methodB() 的事务属性被配置为 PROPAGATION_NESTED, 此时两者之间又将如何协作呢?  ServiceB#methodB 如果 rollback, 那么内部事务(ServiceB#methodB) 将回滚到它执行前的 SavePoint 而外部事务(ServiceA#methodA) 可以有以下两种处理方式:

    捕获异常,执行异常分支逻辑

    方式也是嵌套事务最有价值的地方, 它起到了分支执行的效果, 如果 ServiceB.methodB 失败, 那么执行 ServiceC.methodC(), ServiceB.methodB 已经回滚到它执行之前的 SavePoint, 所以不会产生脏数据(相当于此方法从未执行过), 这种特性可以用在某些特殊的业务中, PROPAGATION_REQUIRED PROPAGATION_REQUIRES_NEW 都没有办法做到这一点。

    2外部事务回滚/提交 代码不做任何修改, 那么如果内部事务(ServiceB#methodB) rollback, 那么首先 ServiceB.methodB 回滚到它执行之前的 SavePoint(在任何情况下都会如此), 外部事务(ServiceA#methodA) 将根据具体的配置决定自己是 commit 还是 rollback

    PROPAGATION_MANDATORY

    支持当前事务,如果当前没有事务,就抛出异常。

    这三种一般很少用到

    PROPAGATION_NOT_SUPPORTED

    以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

    PROPAGATION_NEVER

    以非事务方式执行,如果当前存在事务,则抛出异常。

    参考:

    https://blog.csdn.net/mawenshu316143866/article/details/81281443

    https://blog.csdn.net/weixin_38070406/article/details/78157603

    1.8 spring中注解事务使用常见问题

    问题:使用@Transactional注解事务之后,抛了异常居然不回滚。

    1.8.1注解事务特性

    序号

    说明

    1

    Service类或方法上(一般不建议在接口上)添加@Transcational注解。

    2

    @Transcational只能应用到public可见度方法上,其他可见度不会报错,但不会生效

    3

    默认对unchecked异常(errorruntimeexception)进行事务回滚;如果是checked异常(继承自java.lang.exception的异常,ioexception)则不回滚。

    如对checked异常也回滚,设置rollbackFor=Exception.class

    4

    只读事务

    @Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)

    只读标志只在事务启动时应用,否则即使配置也会被忽略。

    解决注解不回滚

    序号

    说明

    1

    查看方法是否为public

    2

    异常是否为unchecked,

    如对checked异常也回滚,设置@Transactional(rollbackFor=Exception.class)

    类似的还有norollbackFor,自定义不回滚的异常

    3

    数据库引擎要支持事务,如果是MySQL,注意表要使用支持事务的引擎,比如innodb,如果是myisam,事务是不起作用的

    4

    是否开启了对注解的解析 

    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>

    5

    spring是否扫描到你这个包,如下是扫描到org.test下面的包

    <context:component-scan base-package="org.test" ></context:component-scan>

    6

    检查是不是同一个类中的方法调用(如a方法调用同一个类中的b方法)

    ?

    https://blog.csdn.net/qq_30336433/article/details/83338835

    https://blog.csdn.net/wsk1103/article/details/84666050

    https://blog.csdn.net/jiesa/article/details/53438342

    即事务生效通过aop动态代理实现,只有在代理对象间调用时可触发切面逻辑,同一个类种调用的是源对象方法,不经过代理对象所以spring无法切到这次调用,注解事务失效。

    被注解的public方法或者类在被调用的时候,spring会为该public方法或者类中的所有public方法生成一个代理类来代理被注解的方法。

    spring同一个类中,一个方法(未注解@Transactional)调用另外一个注解(@Transactional)方法时,注解失效

    7

    异常是不是被你catch住了

    注意:同一个类中的注解方法互相调用时,注解机制可能是无效的。

    主要是因为通过aop技术创建代理对象触发注解,调用的实际上是代理对象中的方法,而直接使用类中方法则不会触发注解。

    参考:https://www.cnblogs.com/syp172654682/p/9811341.html

    (疑问:如果是这样,那通过一个方法中的多次调用@autowired对象的方法难道不是调用同一个对象?单例情况下)

    测试;

    Service类中的方法insert调用同一个类中的other方法。

    @Service
    
    public class EmployeeService {
    
    @Autowired
    
    EmployeeDao employeeDao;
    
    public Integer insert(Employee emp){
    
    int i=this.other(emp);
    
    return i;
    
    }
    
     
    
    @Transactional(transactionManager="test1TransactionManager")
    
    public Integer other(Employee emp){
    
    int i=employeeDao.insert(emp);
    
    i=i/0;
    
    return i;
    
    }

    由于配置多数据源,因此需要指定事务管理器(transactionManager="test1TransactionManager"

    注意:insert方法未配置事务注解@Transactional,other方法配置了事务注解。

    这种情况下,调用异常,other事务不会回滚。

    原因:虽然上面有网友分析过,但个人理解 虽然使用当前类代理对象的other方法(日志显示为代理对象),但该方法未触发事务增强,因此事务失效。

    场景二:

    @Transactional(transactionManager="test1TransactionManager")
    public Integer insert(Employee emp){
    
    
    @Transactional(transactionManager="test1TransactionManager")
    public Integer other(Employee emp){

    即方法insert也配置事务,此时other会回滚,数据不会入库。因为,spring事务默认传播行为为required,insert已创建事务,就算other方法没有事务也会加入insert的事务。因此会回滚。

    场景三:

    @Transactional(transactionManager="test1TransactionManager")
    public Integer insert(Employee emp){
    
    
    @Transactional(transactionManager="test1TransactionManager")
    public Integer other(Employee emp){
    int i=employeeDao.insert(emp);
    try {
    i=i/0;
    }catch(Exception e) {
    Logger.logMsg(Logger.INFO, "异常");
    }
    return i;

    other方法中捕获异常,很明显,事务不会回滚,因为异常被吃掉。

    就算抛出异常,如下:

    try {
    i=i/0;
    }catch(Exception e) {
    Logger.logMsg(Logger.INFO, "异常");
    throw new Exception();
    }

    事务也不会回滚,因为抛出的异常为checked异常(java.lang.exception),spring事务回滚默认为unchecked异常。因此,可在insert注解事务配置rollbackFor=Exception.class解决

    @Transactional(transactionManager="test1TransactionManager",rollbackFor=Exception.class)
    public Integer insert(Employee emp) throws Exception{
  • 相关阅读:
    网络编程-TCP/IP各层介绍(5层模型讲解)
    TCP、UDP数据包大小的限制
    NAT(地址转换技术)详解(转载)
    用户访问网站基本流程及原理(转载)
    python网络编程相关
    python基础学习笔记——网络编程(协议篇)
    详解Python中的相对导入和绝对导入
    当列表推导式遇到lambda(匿名函数)
    python单例模式的几种实现方法
    用python将多个文档合成一个
  • 原文地址:https://www.cnblogs.com/cslj2013/p/10924755.html
Copyright © 2011-2022 走看看