spring-事务管理

一、基础概念

事务管理是指spring对数据库事务的管理,具有事务属性的是数据库如MySQL。

使用实例

	@Override
	@Transactional
	public ResultData deleteTask(Task task) {
		taskDao.delete(task);
		storeTaskDao.deleteByTaskId(task.getId());
		return ResultData.newSuccessResultData();
	}

只需在方法上加上@Transactional注解就可以进行事务控制

1.编程式和声明式事务

Spring提供了对编程式事务和声明式事务的支持,编程式事务允许用户在代码中精确定义事务的边界如自己需要手动执行commit或者rollback,而声明式事务(基于AOP)有助于用户将操作与事务规则进行解耦。

简单地说,编程式事务侵入到了业务代码里面,但是提供了更加详细的事务管理;
而声明式事务由于基于AOP,所以既能起到事务管理的作用,又可以不影响业务代码的具体实现。

@Transactional注解就是申明式的。

1.@Transactional注解的原理

Spring事务底层是基于数据库事务和AOP机制的

1.在一个方法上加了@Transactional注解后,Spring会基于这个类生成一个代理对象,会将这个代理对象作为bean。

2.当在使用这个代理对象的方法时,如果这个方法上存在@Transactional注解,那么代理则利⽤事务管理器创建⼀个数据库连接。

3.修改数据库连接的autocommit属性为false,禁⽌此连接的⾃动提交,这是实现Spring事务⾮常重 要的⼀步

4.然后执⾏当前⽅法,⽅法中会执⾏sql

5.执⾏完当前⽅法后,如果没有出现异常就直接提交事务,如果出现了异常,并且这个异常是需要回滚的就会回滚事务,否则仍然提交事务

当然,针对哪些异常回滚事务是可以配置的,可以利用@Transactional注解中的rollbackFor属性进行配置,默认情况下会对RuntimeException和Error进行回滚。

二、spring事务管理架构设计

Spring事务管理的实现有许多细节,如果对整个接口框架有个大体了解会非常有利于我们理解事务,下面通过讲解Spring的事务接口来了解Spring实现事务的具体策略。

Spring事务管理涉及的接口的结构如下:

spring-事务管理_第1张图片

2.1事务管理器

spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。

Spring事务管理器的接口是 org.springframework.transaction.PlatformTransactionManager

通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。此接口的内容如下:

public interface PlatformTransactionManager {

    // 由TransactionDefinition得到TransactionStatus对象
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; 
    // 提交
    Void commit(TransactionStatus status) throws TransactionException;  
    // 回滚
    Void rollback(TransactionStatus status) throws TransactionException;  

}

1.JDBC事务

如果应用程序中直接使用JDBC来进行持久化,DataSourceTransactionManager会为你处理事务边界。
为了使用DataSourceTransactionManager,你需要使用如下的XML将其装配到应用程序的上下文定义中:

   <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    bean>

实际上,DataSourceTransactionManager是通过调用java.sql.Connection来管理事务,通过调用连接的commit()方法来提交事务,同样,事务失败则通过调用rollback()方法进行回滚。

2.2 事务属性定义TransactionDefinition

上面讲到的事务管理器接口PlatformTransactionManager通过getTransaction(TransactionDefinition definition)方法来得到事务,这个方法里面的参数是TransactionDefinition类,这个类就定义了一些基本的事务属性。

那么什么是事务属性呢?

事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。
事务属性包含了5个方面,如图所示:

spring-事务管理_第2张图片

1.传播行为

什么叫事务传播行为?

听起来挺高端的,其实很简单。 即然是传播,那么至少有两个东西,才可以发生传播。单体不存在传播这个行为。

事务传播行为(propagation behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。

例如:methodA事务方法调用methodB事务方法时,methodB是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。

spring的七种传播行为如下:其中spring默认的传播行为是PROPAGATION_REQUIRED。

spring-事务管理_第3张图片

简洁版:

PROPAGATION_REQUIRED–支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS–支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY–必须有当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW–新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED–以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER–以非事务方式执行,如果当前存在事务,则抛出异常。

理解顺序建议为:PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_SUPPORTS,PROPAGATION_NOT_SUPPORTED,PROPAGATION_NEVER,PROPAGATION_SUPPORTS。

1.PROPAGATION_REQUIRED

有一个事务失败,则全失败

表示当前方法必须运行在事务中。如果当前方法在事务中存在,则运行在当前事务中。如果没有包含在事务中,则开启一个事务。

自己理解:当前方法如果没有在其他事务中,则自己开启一个事务,如果当前方法在其他事务中,则加入到其他事务中。

//事务属性 PROPAGATION_REQUIRED
methodA{
    ……
    methodB();
    ……
}
//事务属性 PROPAGATION_REQUIRED
methodB{
   ……
}

单独调用methodB方法:

main{ 
    metodB(); 
} 

相当于

Main{ 
    Connection con=null; 
    try{ 
        con = getConnection(); 
        con.setAutoCommit(false); 

        //方法调用
        methodB(); 

        //提交事务
        con.commit(); 
    } Catch(RuntimeException ex) { 
        //回滚事务
        con.rollback();   
    } finally { 
        //释放资源
        closeCon(); 
    } 
} 

Spring保证在methodB方法中所有的调用都获得到一个相同的连接。在调用methodB时,没有一个存在的事务,所以获得一个新的连接,开启了一个新的事务。

单独调用MethodA时,在MethodA内又会调用MethodB.
执行效果相当于:

main{ 
    Connection con = null; 
    try{ 
        con = getConnection(); 
        methodA(); 
        con.commit(); 
    } catch(RuntimeException ex) { 
        con.rollback(); 
    } finally {    
        closeCon(); 
    }  
} 

调用MethodA时,环境中没有事务,所以开启一个新的事务.
当在MethodA中调用MethodB时,环境中已经有了一个事务,所以methodB就加入当前事务。
注意A和B这里虽然在一个事务中,但A和B分别拥有的单独数据源的连接

执行流程如下:

spring-事务管理_第4张图片

2.PROPAGATION_REQUIRES_NEW

总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。

事务与事务之间相互独立,一个失败了不影响另一个事务

流程如下:
spring-事务管理_第5张图片

spring-事务管理_第6张图片

//事务属性 PROPAGATION_REQUIRED
methodA(){
    doSomeThingA();
    methodB();
    doSomeThingB();
}

//事务属性 PROPAGATION_REQUIRES_NEW
methodB(){
    ……
}


调用A方法:

main(){
    methodA();
}

相当于

main(){
    TransactionManager tm = null;
    try{
        //获得一个JTA事务管理器
        tm = getTransactionManager();
        tm.begin();//开启一个新的事务
        Transaction ts1 = tm.getTransaction();
        doSomeThing();
        tm.suspend();//挂起当前事务
        try{
            tm.begin();//重新开启第二个事务
            Transaction ts2 = tm.getTransaction();
            methodB();
            ts2.commit();//提交第二个事务
        } Catch(RunTimeException ex) {
            ts2.rollback();//回滚第二个事务
        } finally {
            //释放资源
        }
        //methodB执行完后,恢复第一个事务
        tm.resume(ts1);
        doSomeThingB();
        ts1.commit();//提交第一个事务
    } catch(RunTimeException ex) {
        ts1.rollback();//回滚第一个事务
    } finally {
        //释放资源
    }
}

在这里,我把ts1称为外层事务,ts2称为内层事务。
从上面的代码可以看出,ts2与ts1是两个独立的事务,互不相干。Ts2是否成功并不依赖于 ts1。如果methodA方法在调用methodB方法后的doSomeThingB方法失败了,而methodB方法所做的结果依然被提交,而除了 methodB之外的其它代码导致的异常,ts1将之回滚自己的。

使用PROPAGATION_REQUIRES_NEW,需要使用 JtaTransactionManager作为事务管理器

3.PROPAGATION_NESTED

如果一个活动的事务存在,则运行在一个嵌套的事务中。 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行。

嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。

spring-事务管理_第7张图片

main(){
    Connection con = null;
    Savepoint savepoint = null;
    try{
        con = getConnection();
        con.setAutoCommit(false);
        doSomeThingA();
        savepoint = con2.setSavepoint();
        try{
            methodB();
        } catch(RuntimeException ex) {
            con.rollback(savepoint);
        } finally {
            //释放资源
        }
        doSomeThingB();
        con.commit();
    } catch(RuntimeException ex) {
        con.rollback();
    } finally {
        //释放资源
    }
}

当methodB方法调用之前,调用setSavepoint方法,保存当前的状态到savepoint。
如果methodB方法调用失败,则恢复到之前保存的状态。
但是需要注意的是,这时的事务并没有进行提交,如果后续的代码(doSomeThingB()方法)调用失败,则回滚包括methodB方法的所有操作,如果成功只需回滚methodB,其他正常提交。

具体例子和原理请参考:
1.Spring事务管理(详解+实例)https://blog.csdn.net/trigl/article/details/50968079
2.浅析Spring事务传播行为和隔离级别 https://www.cnblogs.com/zsychanpin/p/7074071.html

2. 隔离级别

事务的第二个维度就是隔离级别(isolation level)。
隔离级别定义了一个事务可能受其他并发事务影响的程度。

spring隔离级别如下:
spring-事务管理_第8张图片

3 只读

事务的第三个特性是它是否为只读事务。
通过将事务设置为只读即设置了readonly后,connection都会被赋予readonly,效果取决于数据库的实现。
如在mysql下测试,发现支持readOnly,设置为true时,只能查询,若增删改会异常:
在oracle下测试,发现不支持readOnly,也就是不论Connection里的readOnly属性是true还是false均不影响SQL的增删改查;

4 事务超时

为了使应用程序很好地运行,事务不能运行太长的时间。
因为事务可能涉及对后端数据库的锁定,所以长时间的事务会不必要的占用数据库资源。
事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。

事务太长危害场景

1.当出现并发调用到配置事务的接口时,会不断占有连接池资源而不释放,可能造成其他接口获取不到数据库链接和发生超时错误等,系统的吞吐量就大大下降甚至不可用。

5 回滚规则

事务五边形的最后一个方面是一组规则,这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚(这一行为与EJB的回滚行为是一致的)

但是你可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。
同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。

2.3事务失效

1.spring事务什么时候会失效?

spring事务的原理是AOP,进行了切面增强,那么失效的根本原因是这个AOP不起作用了!常见情况有如下几种

1.发生自调用

类里面使用this调用本类的方法(this通常省略),此时这个this对象不是代理类,而是类本身!

@Service
public class DemoService {

    public  void query(Demo demo) {
        save(demo);
    }
    
    @Transactional
    public void save(Demo demo) {
        
    }
    
}

可以看到,query方法调用了save的方法,由于spring的事务实现是因为aop生成代理,这样是直接调用了this对象,所以也不会生成事务。

但下面这种query方法上面如果有@Transactional注解,即使调用的是this方法,事务也是会生效的。因为DemoService.query方法是开启了事务的。

@Service
public class DemoService {

    @Transactional
    public  void query(Demo demo) {
        save(demo);
    }
    
    @Transactional
    public void save(Demo demo) {
        
    }
    
}

解决方法

1、增加一个service,把一个事务的方法移到新增加的service方法里面,然后进行注入再调用

@Service
public class DemoTwoService {
    
    @Transactional
    public void save(Demo demo) {

    }
}

@Service
public class DemoService {

    @Autowired
    DemoTwoService demoTwoService;

    public  void query(Demo demo) {
        demoTwoService.save(demo);
    }
}

2.使用:AopContext.currentProxy()

@Service
public class DemoService {
    
    public  void query(Demo demo) {
        DemoService demoService = (DemoService)AopContext.currentProxy();
        demoService.save(demo);
    }

    @Transactional
    public void save(Demo demo) {

    }
}

如果使用上述方案报如下异常:Cannot find current proxy: Set ‘exposeProxy’ property on Advised to ‘true’ to make it available,可以采用下面的更优雅的方案:

@Service
public class DemoService {
    
    public  void query(Demo demo) {
        DemoService demoService = SpringUtil.getBean(this.getClass());
        demoService.save(demo);
    }

    @Transactional
    public void save(Demo demo) {

    }
}
2、方法不是public的或者使用final,static等关键字

因为底层cglib是基于⽗⼦类来实现的, ⼦类是不能重写⽗类的private⽅法的,所以⽆法很好的利⽤代理,也会导致@Transactianal失效。
同理,final无法被继承,静态方法是属于类的,而不是属于对象的,无法重写静态方法所以也就不可能实现事务

3、数据库不支持事务
4、没有被spring管理

在使用spring事务的时候,对象要被spring进行管理,也就是需要创建bean,一般我们都会加@Controller、@Service、@Component、@Repository等注解,可以自动实现bean实例化和依赖注入的功能。,如果忘记加了,也会导致,事务的失效

5、使用默认的事务处理方式

异常被吃掉,事务不会回滚(或者抛出的异常没有被定义,默认为RuntimeException)。

spring的事务默认是对RuntimeException进行回滚,而不继承RuntimeException的不回滚。因为在java的设计中,它认为不继承RuntimeException的异常是”checkException”或普通异常,如IOException,这些异常在java语法中是要求强制处理的。对于这些普通异常,spring默认它们都已经处理,所以默认不回滚。可以添加rollbackfor=Exception.class来表示所有的Exception都回滚。

思考

1.数据库的配置隔离级别是Read Commited,而Spring配置的隔离级别是Repeatable Read,请问这时隔离级别是以哪一个为准?

以Spring配置的为准,如果spring设置的隔离级别数据库不支持,效果取决于数据库

参考资料

1.Spring事务管理(详解+实例)https://blog.csdn.net/trigl/article/details/50968079
2.浅析Spring事务传播行为和隔离级别https://www.cnblogs.com/zsychanpin/p/7074071.html
3.Spring事务失效的各种场景(13种) https://www.jb51.net/article/256104.htm

你可能感兴趣的:(spring,spring)