天天看点

spring中常见事务失效处理,总结、分析

spring中常见事务失效处理分析

  • ​​写在前面​​
  • ​​一、事务基础​​
  • ​​1.1、定义:​​
  • ​​1.2、四大特性(A-C-I-D)​​
  • ​​1.3、常用事务管理​​
  • ​​二、Spring 事务​​
  • ​​2.1、Spring中事务基础​​
  • ​​2.2、要注意的地方​​
  • ​​2.2、常见异常​​
  • ​​2.3、Spring中常用事务处理操作 / 场景,要注意的地方​​
  • ​​2.3.1、 捕捉异常, 导致不能回滚​​
  • ​​2.3.2、 自定义异常,导致事务不回滚​​
  • ​​2.3.3、 嵌套方法​​
  • ​​2.3.4、 同一个类中, 一个不标注事务的方法去调用 transactional 的方法, 事务会失效​​
  • ​​三、Mysql 事务​​
  • ​​四、Oracle 事务​​
  • ​​五、Nosql 事务​​
  • ​​六、分布式事务​​
  • ​​6.1、出现原因​​
  • ​​6.2、JTA​​
  • ​​七、总结​​

写在前面

只是总结,分析

一、事务基础

1.1、定义:

数据库几乎是所有系统的核心模块,它将数据有条理地保存在储存介质(磁盘)中,

并在逻辑上,将数据以结构化的形态呈现给用户。支持数据的增、删、改、查,并在过程中保障数据的正确且可靠。

1.2、四大特性(A-C-I-D)

  • 原子性(Atomicity): 事务要么全部完成,要么全部取消。 如果事务崩溃,状态回到事务之前(事务回滚)。
  • 隔离性(Isolation): 如果2个事务 T1 和 T2 同时运行,事务 T1 和 T2 最终的结果是相同的,不管 T1和T2谁先结束。
  • 持久性(Durability): 一旦事务提交,不管发生什么(崩溃或者出错),数据要保存在数据库中。
  • 一致性(Consistency): 只有合法的数据(依照关系约束和函数约束)才能写入数据库。

场景分析

如何同时保证上述交易中,A账户总金额减少一个亿,B账户总金额增加一个亿? A

A账户如果同时在和C账户交易(T2),如何让这两笔交易互不影响? I

如果交易完成时数据库突然崩溃,如何保证交易数据成功保存在数据库中? D

如何在支持大量交易的同时,保证数据的合法性(没有钱凭空产生或消失) ? C

1.3、常用事务管理

主要包括,以下几种

  • Mysql 事务
  • Oracle 事务
  • Nosql 事务
  • Spring 事务
  • 分布式 事务

二、Spring 事务

2.1、Spring中事务基础

Spring中事务基础很简单,两种方式配置事务,XML或者注解方式,以下基于注解方式,介绍

实现原理:基于Spring AOP,可自行学习

七种事务机制

1. TransactionDefinition.PROPAGATION_REQUIRED:
   如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
 
2. TransactionDefinition.PROPAGATION_REQUIRES_NEW:
   创建一个新的事务,如果当前存在事务,则把当前事务挂起。
 
3. TransactionDefinition.PROPAGATION_SUPPORTS:
   如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
 
4. TransactionDefinition.PROPAGATION_NOT_SUPPORTED:
   以非事务方式运行,如果当前存在事务,则把当前事务挂起。
 
5. TransactionDefinition.PROPAGATION_NEVER:
   以非事务方式运行,如果当前存在事务,则抛出异常。
 
6. TransactionDefinition.PROPAGATION_MANDATORY:
   如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
 
7. TransactionDefinition.PROPAGATION_NESTED:
   如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;
   如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。      

2.2、要注意的地方

  1. 默认事务:

Propagation propagation() default Propagation.REQUIRED

当然,可在注解中,根据需要,配置事务机制

  1. 声明位置:

类上,所有方法默认事务,不推荐,影响了查询效率(AOP)

方法上,该方法默认事务,但也要注意细节,包括修饰符,嵌套事务,异常处理等等,都会影响事务是否正确配置

  1. 修饰符
方法必须 public 修饰
  1. 异常处理
系统中的异常处理方式,很多,主要 return,throw ,try()catch(){}三种方式,只有throw的异常,才会触发事务
  1. 不同类嵌套事务

不同类之间的方法调用,如类A的方法a()调用类B的方法b(),这种情况事务是正常起作用的。只要方法a()或b()配置了事务,运行中就会开启事务,产生代理。

若两个方法都配置了事务,两个事务具体以何种方式传播,取决于设置的事务传播特性。

  1. 同类嵌套事务
@Service
    public class TestImpl implements TestService {
        @Autowired
        private Dao dao;

        public void insertUser(User user) {
            dao.insertUser(user);
            this.selectUser(user.getId());
        }

        @Transactional
        public String selectUser(int id) {
            throw new RuntimeException();
        }
    }      

这种同类中,事务方法的调用问题,默认情况下,只有来自外部的方法调用才会被AOP代理捕获,即是,类内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用@Transactional注解进行修饰

解决方案:五种

方法1:将事务方法放到另一个类中(或者单独开启一层,取名“事务层”)进行调用,即符合了在对象之间调用的条件。

方法2:获取本对象的代理对象,再进行调用.

  1. Spring-content.xml上下文中,增加配置:<aop:aspectj-autoproxy expose-proxy=“true”/>
  2. 在xxxServiceImpl中,用(xxxService)(AopContext.currentProxy()),获取到xxxService的代理类,再调用事务方法,强行经过代理类,激活事务切面。

方法3:

很多时候,方法内调用又希望激活事务,是由于同一个方法既有DAO操作又有I/O等耗时操作,不想让耗时的I/O造成事务的太长耗时(比如新增商品同时需要写入库存)。此时,可以将I/O做成异步操作(如加入线程池),而加入线程池的操作即便加入事务也不会导致事务太长,问题可以迎刃而解。

方法4:手写事务

自行学习

方法5:

接口内部方法调用的错误实现,可以用AspectJ 取代 Spring AOP 代理

2.2、常见异常

  • Connection is read-only,…操作只读事务异常
  • UnexpectedRollbackException: Transaction rolled … 方法嵌套事务异常
  • zzz
  • xxx

2.3、Spring中常用事务处理操作 / 场景,要注意的地方

2.3.1、 捕捉异常, 导致不能回滚

代码示例,此处事务不会回滚

@Override
    @Transactional
    public void CatchExceptionCanNotRollback() {
        try {
            extraAdDao.save(new ExtraAd("otii"));
            throw new RuntimeException();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }      

解决方案 :

  • 1、手动处理如下
@Override
    @Transactional
    public void CatchExceptionCanNotRollback() {

        try {
            extraAdDao.save(new ExtraAd("otii"));
            throw new RuntimeException();
        } catch (Exception ex) {
            ex.printStackTrace();
            // 手动标记回滚
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
    }      
  • 2、不捕获
@Override
    @Transactional
    public void RuntimeExceptionCanRollback() {
        extraAdDao.save(new ExtraAd("otii"));
        throw new RuntimeException();
    }      

2.3.2、 自定义异常,导致事务不回滚

代码示例,无法回滚

@Override
    @Transactional
    public void NotRuntimeExceptionCanNotRollback() throws CustomException {
        try {
            extraAdDao.save(new ExtraAd("otii"));
            throw new RuntimeException();
        } catch (Exception ex) {
            throw new CustomException(ex.getMessage());
        }
    }      

解决方案 :

  • 1、异常转换
@Override
    @Transactional(rollbackFor = {CustomException.class})
    public void AssignExceptionCanRollback() throws CustomException {
        try {
            extraAdDao.save(new ExtraAd("otii"));
            throw new RuntimeException();
        } catch (Exception ex) {
            throw new CustomException(ex.getMessage());
        }
    }      

注意:这样 指定也是可以的

@Override
    @Transactional(rollbackFor = {Exception.class})
    public void AssignExceptionCanRollback() throws CustomException {
        try {
            extraAdDao.save(new ExtraAd("otii"));
            throw new RuntimeException();
        } catch (Exception ex) {
            throw new CustomException(ex.getMessage());
        }
    }      

2.3.3、 嵌套方法

代码示例,此处事务 不回滚

public void oneSaveMethod() {
        extraAdDao.save(new ExtraAd("otii"));
    }

    @Override
    @Transactional
    public void RollbackOnlyCanRollback() throws Exception {
        oneSaveMethod();
        throw new Exception("");
    }      

解决方案:

  • 1、事务回滚 - 异常指定
public void oneSaveMethod() {
        extraAdDao.save(new ExtraAd("otii"));
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void RollbackOnlyCanRollback() throws Exception {
        oneSaveMethod();
        throw new Exception("");
    }      
  • 2、配置事务传播机制
public void oneSaveMethod() {
        extraAdDao.save(new ExtraAd("otii"));
    }
    
    @Override
    @Transactional(propagation = Propagation.MANDATORY)
    public void RollbackOnlyCanRollback() throws CustomException {
        oneSaveMethod();
        throw new CustomException("");
    }      

2.3.4、 同一个类中, 一个不标注事务的方法去调用 transactional 的方法, 事务会失效

代码示例,如下,事务不回滚

@Override
    public void NonTransactionalCanNotRollback() {
        anotherOneSaveMethod();
    }
    
    @Transactional
    public void anotherOneSaveMethod() {
        extraAdDao.save(new ExtraAd("otii"));
        throw new RuntimeException();
    }      

这里会有奇怪的现象,就是 会莫名 commit

解决方案 :

  • 1、调用事务方法时,加上 @Transactional 注解,即可解决

三、Mysql 事务

待记录

四、Oracle 事务

待记录

五、Nosql 事务

六、分布式事务

6.1、出现原因

6.2、JTA

JTA(Java Transaction API)提供了跨数据库连接(或其他JTA资源)的事务管理能力。JTA事务管理则由JTA容器实现,J2ee框架中事务管理器与应用程序,资源管理器,以及应用服务器之间的事务通讯。

还有

七、总结