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{

 

posted on 2019-05-26 00:24  cslj2013  阅读(233)  评论(0编辑  收藏  举报

导航