基本介绍

  事务是数据一致性最基本的保证,也就是说一个事务中的操作要么都成功,要么都失败,不允许部分成功。我们常说的事务就是jdbc事务,当然Java中还有其他事务,并且在使用jdbc事务有很多注意点,请详细了解“注意点”中的内容。但是这里有个误区,因为我们一般是使用spring的注解@Transactional来实现事务,所以很多人会认为spring提供了事务,其实spring本身并没有提供事务,它只是对jdbc的事务进行了封装,然后通过AOP动态代理来实现事务的功能,这样简化了jdbc事务调用的相关步骤,让我们更专注于业务功能。

  注意关键词“动态代理”,这意味着要生成一个代理类,那么我们就不能在一个类内直接调用事务方法,否则无法代理,而且该事务方法必须是public,如果定义成 protected、private 或者默认可见性,则无法调用!

实现原理

注意点(容易引起事务不生效)

  • 使用的数据库引擎是否支持事务
  • 加事务的方法必须是public,否则事务不起作用(这一点由Spring的AOP特性决定的,理论上而言,不public也能切入,但spring可能是觉得private自己用的方法,应该自己控制,不应该用事务切进去吧)。另外private 方法, final 方法 和 static 方法不能添加事务,加了也不生效
  • 处理的业务和事务的入口必须在一个线程内,否则事务不生效
  • 对于jdbc事务而言,必须是一个connection中才有效的
  • Spring的事务管理默认只对出现运行期异常(java.lang.RuntimeException及其子类)进行回滚(至于为什么spring要这么设计:因为spring认为Checked的异常属于业务的,coder需要给出解决方案而不应该直接扔该框架)。如果业务需要,一定要抛出checked异常的话,可以通过rollbackFor属性指定异常类型即可。
  • 确认调用的类是否被代理了
  • 在本类中调用另一个带有事务的方法,事务时不生效的。这个时最容易犯的错误,不生效的原因主要是和事务的实现原理有关
  • @EnableTransactionManagement // 启注解事务管理,等同于xml配置方式的 <tx:annotation-driven />备注:本系列所有博文的讨论都针对于springboot而不再对spring做说明
  • 事务注解不能注到接口上,要写到具体类上,否者不生效

类型

  • JDBC事务
  • JAT事务
  • 容器事务

特性

  • 原子性:一个事务中所有对数据库的操作是一个不可分割的操作序列,要么全做,要么全部做。
  • 一致性:数据不会因为事务的执行而遭到破坏。
  • 隔离性:一个事务的执行,不受其他事务(进程)的干扰。既并发执行的个事务之间互不干扰。
  • 持久性:一个事务一旦提交,它对数据库的改变将是永久的。

7种传播属性

  

  • PROPAGATION_REQUIRED -- 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
  • PROPAGATION_SUPPORTS -- 支持当前事务,如果当前没有事务,就以非事务方式执行。
  • PROPAGATION_MANDATORY -- 支持当前事务,如果当前没有事务,就抛出异常。
  • PROPAGATION_REQUIRES_NEW -- 新建事务,如果当前存在事务,把当前事务挂起。会启动一个独立的新事务,这个事务将被完全 commited 或 rollback 而不依赖于外部事务,它拥有自己的隔离范围,自己的锁等等。当内部事务开始执行时,外部事务将被挂起,内务事务结束时,外部事务将继续执行
  • PROPAGATION_NOT_SUPPORTED -- 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • PROPAGATION_NEVER -- 以非事务方式执行,如果当前存在事务,则抛出异常。
  • PROPAGATION_NESTED -- 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。会开始一个 "嵌套的" 事务,它是已经存在事务的一个真正的子事务。 嵌套事务开始执行时,它将取得一个savepoint,如果这个嵌套事务失败,将回滚到此savepoint。 嵌套事务是外部事务的一部分,只有外部事务结束后它才会被提交,这个规则同样适用于rollback。
前六个策略类似于EJB CMT,第七个(PROPAGATION_NESTED)是Spring所提供的一个特殊变量。 它要求事务管理器或者使用JDBC 3.0 Savepoint API提供嵌套事务行为(如Spring的DataSourceTransactionManager)

基本场景

图一:事务不生效:.@Transactional的事务开启 ,或者是基于接口的 或者是基于类的代理被创建。所以在同一个类中一个无事务的方法调用另一个有事务的方法,事务是不会起作用的(这就是业界老问题:类内部方法调用事务不生效的问题原因)。

图二:事务生效

图三:事务生效

图四:事务生效

图五:事务生效(稍微解释一下,这里虽然是方法内部调用,但是事务切入了addInfo方法,所以即使内部抛出异常,也是可以生效的。当年我竟然惊讶,看来还是太年轻,哈哈)

图六:事务不生效(准确的说这叫没有事务)

图七:如何使本类中调用方法的事务生效。

  这是我们解决方法内部调用事务不生效的最常用方法之一:内部维护一个注入自己的Bean,然后使用这个属性来调用方法。其实还有一种方法,那就是利用Aop上下文来获取代理对象(((TestService)AopContext.currentProxy()).create(); ),然后通过代理对象来调用。这里需要注意:Aop上下文spring默认是关闭的,需要手动开启:<aop:aspectj-autoproxy expose-proxy="true"/>

嵌套事务

 七种事务传播属性的应用

1: REQUIRED
加入当前正要执行的事务不在另外一个事务里,那么就起一个新的事务:
  比如说,DemoServiceB.demoMethodB的事务级别定义为REQUIRED, 那么由于执行DemoServiceA.demoMethodA的时候,DemoServiceA.demoMethodA已经起了事务,这时调用DemoServiceB.demoMethodB,
DemoServiceB.demoMethodB看到自己已经运行在DemoServiceA.demoMethodA的事务内部,就不再起新的事务。而假如DemoServiceA.demoMethodA运行的时候发现自己没有在事务中,他就会为自己分配一个事务。
这样,在DemoServiceA.demoMethodA或者在DemoServiceB.demoMethodB内的任何地方出现异常,事务都会被回滚。即使DemoServiceB.demoMethodB的事务已经被提交,但是DemoServiceA.demoMethodA在接下来fail要回滚,DemoServiceB.demoMethodB也要回滚
  REQUIRED保证其处理过程同一个事务,如果调用的同一个类的配置的REQUIRED的方法,且此方法存在TRY CATCH代码块, 如果此代码块出现异常,程序可以继续执行。这其实使因为同类调用的方法的事务根本不生效。
  但如果调用的其他类的配置REQUIRED方法,且TRY CATCH住,则全部的提交全部回滚,且报出异常:Transaction rolled back because it has been marked as rollback-only因为事务报出异常后要全部回滚,包括父类的调用。
  如果service中包含多个dao的方法,其都属于同一个事务,其中报错全部回滚,try catch住不影响程序代码的继续执行.
2: SUPPORTS
如果当前在事务中,即以事务的形式运行,如果当前不再一个事务中,那么就以非事务的形式运行
3: MANDATORY
必须在一个事务中运行。也就是说,他只能被一个父事务调用。否则,他就要抛出异常
4: REQUIRES_NEW
这个就比较绕口了。 比如我们设计DemoServiceA.demoMethodA的事务级别为REQUIRED,DemoServiceB.demoMethodB的事务级别为REQUIRES_NEW,
那么当执行到DemoServiceB.demoMethodB的时候,DemoServiceA.demoMethodA所在的事务就会挂起,DemoServiceB.demoMethodB会起一个新的事务,等待DemoServiceB.demoMethodB的事务完成以后,
他才继续执行。他与REQUIRED 的事务区别在于事务的回滚程度了。因为DemoServiceB.demoMethodB是新起一个事务,那么就是存在
两个不同的事务。如果DemoServiceB.demoMethodB已经提交,那么DemoServiceA.demoMethodA失败回滚,DemoServiceB.demoMethodB是不会回滚的。如果DemoServiceB.demoMethodB失败回滚,
如果他抛出的异常被DemoServiceA.demoMethodA捕获,DemoServiceA.demoMethodA事务仍然可能提交。
5: NOT_SUPPORTED
当前不支持事务。比如DemoServiceA.demoMethodA的事务级别是REQUIRED ,而DemoServiceB.demoMethodB的事务级别是NOT_SUPPORTED ,
那么当执行到DemoServiceB.demoMethodB时,DemoServiceA.demoMethodA的事务挂起,而他以非事务的状态运行完,再继续DemoServiceA.demoMethodA的事务。
6: NEVER
不能在事务中运行。假设DemoServiceA.demoMethodA的事务级别是REQUIRED, 而DemoServiceB.demoMethodB的事务级别是NEVER ,
那么DemoServiceB.demoMethodB就要抛出异常了。
7: NESTED
理解Nested的关键是savepoint。他与REQUIRES_NEW的区别是,REQUIRES_NEW另起一个事务,将会与他的父事务相互独立,
而Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。也就是说,如果父事务最后回滚,他也要回滚的。
例如:类serviceA和其中的方法methodA,类serviceB和其中的方法methodB,并且methodA的事务类型为REQUIRED,methodB的事务类型为NESTED.
调用代码如下:
代码一:
ServiceA { /**
* 事务属性配置为 PROPAGATION_REQUIRED
*/
void methodA() {
ServiceB.methodB();
} } ServiceB { /**
* 事务属性配置为 PROPAGATION_REQUIRES_NEW
*/
void methodB() {
} }

改成:

代码二:
ServiceA { /**
* 事务属性配置为 PROPAGATION_REQUIRED
*/
void methodA() {
try {
ServiceB.methodB();
} catch (SomeException) {
// 执行其他业务, 如 ServiceC.methodC();
}
} }

  这种方式也是潜套事务最有价值的地方, 它起到了分支执行的效果, 如果 ServiceB.methodB 失败, 那么执行 ServiceC.methodC(), 而 ServiceB.methodB 已经回滚到它执行之前的 SavePoint, 所以不会产生脏数据(相当于此方法从未执行过), 这种特性可以用在某些特殊的业务中, 而 PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 都没有办法做到这一点. (题外话 : 看到这种代码, 似乎似曾相识, 想起了 prototype.js 中的 Try 函数 )

  如果代码不做任何修改(代码一), 那么如果内部事务(即 ServiceB#methodB) rollback, 那么首先 ServiceB.methodB 回滚到它执行之前的 SavePoint(在任何情况下都会如此), 外部事务(即 ServiceA#methodA) 将根据具体的配置决定自己是 commit 还是 rollback (+MyCheckedException),也就是如果设置了rollbackfor,并且methodB抛出的异常符合rollbackfor设置的异常,那么methodA就会回滚,否者不会滚。

 


学习链接

jdbc事务和JAT事务管理

spring事务和JDBC事务的关系

事务的实现原理

Java之事务的基本应用的更多相关文章

  1. Java中事务总结详解(精华)

    1.什么是JAVA事务? 通常的观念认为,事务仅与数据库相关. 事务必须服从ISO/IEC所制定的ACID原则.ACID是原子性(atomicity).一致性(consistency).隔离性 (is ...

  2. JAVA分布式事务原理及应用(转)

      JTA(Java Transaction API)允许应用程序执行分布式事务处理--在两个或多个网络计算机资源上访问并且更新数据. JDBC驱动程序的JTA支持极大地增强了数据访问能力. 本文的目 ...

  3. JAVA分布式事务原理及应用

    JTA(Java Transaction API)允许应用程序执行分布式事务处理--在两个或多个网络计算机资源上访问并且更新数据.JDBC驱动程序的JTA支持极大地增强了数据访问能力. 本文的目的是要 ...

  4. java的事务类型及定义

    转载: 什么是事务: 首先,说说什么事务.我认为事务,就是一组操作数据库的动作集合. 事务是现代数据库理论中的核心概念之一.如果一组处理步骤或者全部发生或者一步也不执行,我们称该组处理步骤为一个事务. ...

  5. Java 默认事务级别read committed对binlog_format的需求

    转载: java.sql.SQLException: Cannot execute statement: impossible to write to binary log since BINLOG_ ...

  6. [java][db]JAVA分布式事务原理及应用

    JTA(Java Transaction API)同意应用程序运行分布式事务处理--在两个或多个网络计算机资源上訪问而且更新数据.JDBC驱动程序的JTA支持极大地增强了数据訪问能力.  本文的目的是 ...

  7. Java 分布式事务

    0 引言 本文主要介绍java中分布式事务以及对应的解决方案. 1 分布式事务产生的原因 1.1 数据库分库分表 当数据库单表一年产生的数据超过1000W,那么就要考虑分库分表,具体分库分表的原理在此 ...

  8. Java多线程事务管理

    今天要讨论的是"Java实现多线程单条数据事务管理",在此之前,顺便回顾一下实现多线程的几种方式 实现多线程的三种方式 一.继承Thread类 第一种方法是继承Thread类,重写 ...

  9. Java中事务的概念

    | 版权声明:本文为博主原创文章,未经博主允许不得转载. 前言: 在学习事务确实有不少弯路,那么今天笔者就用例子讲解一下事务,让初学者少走一些弯路. [回顾事务]问:什么是事务? 答:用专业术语来说事 ...

随机推荐

  1. SELECT INTO和INSERT INTO SELECT的区别 类似aaa?a=1&b=2&c=3&d=4,如何将问号以后的数据变为键值对 C# 获取一定区间的随即数 0、1两个值除随机数以外的取值方法(0、1两个值被取值的概率相等) C# MD5 加密,解密 C#中DataTable删除多条数据

    SELECT INTO和INSERT INTO SELECT的区别   数据库中的数据复制备份 SELECT INTO: 形式: SELECT value1,value2,value3 INTO Ta ...

  2. 分布式任务&分布式锁

    目前系统中存在批量审批.批量授权等各个操作,批量操作中可能因为处理机器.线程不同,造成刷新缓存丢失授权等信息,如批量审批同一用户权限多个权限申请后,流程平台并发的发送多个http请求到acl不同服务器 ...

  3. linux 编译运行c文件

    在ubuntu安装gcc 编辑 test.c /* Not stdio.h */ #include <unistd.h> void main() { char str[100]; /*Wr ...

  4. [转]谈NAND Flash的底层结构和解析

    这里我想以一个纯玩家的角度来谈谈关于NAND Flash的底层结构和解析,可能会有错误的地方,如果有这方面专家强烈欢迎指正. NAND Flash作为一种比较实用的固态硬盘存储介质,有自己的一些物理特 ...

  5. winscp的root连接ubuntu“拒绝访问”的解决方法

    转载:https://www.cnblogs.com/weizhxa/p/10098640.html 解决: 1.修改ssh配置文件:sudo vim etc/ssh/sshd_config 在#Pe ...

  6. FatFs文件系统的移植

    FatFs 的底层可以写一次命令,读写多个扇区.FatFs的设计的读写的思想就很好,小块的数据,我就经过Buffer来存储,大块的数据,我就直接进行存取,那样速度,效率高了很多,看图: FatFs文件 ...

  7. git rebase 的使用

    rebase 在 Git 中整合来自不同分支的修改主要有两种方法:merge 以及 rebase. 在本节中我们将学习什么是“rebase”,怎样使用“rebase”,并将展示该操作的惊艳之处,以及指 ...

  8. Oracle字段根据逗号分割查询数据

    需求是表里的某个字段存储的值是以逗号分隔开来的,要求根据分隔的每一个值都能查出来数据,但是不能使用like查询. 数据是这样的: 查询的sql如下: select * from ( select gu ...

  9. 用SUMIF对超15位的代码进行条件求和,出错了,原因是....

    用SUMIF对超15位的代码进行条件求和,出错了,原因是.... 2017-10-29 23:01 一.问题 有读者朋友问: 用SUMIF进行条件求和时,如果统计的条件是超15位的代码,就会出错,比如 ...

  10. Java | 原来 serialVersionUID 的用处在这里

    本文首发于 http://youngzy.com/ 一直不太明白Java对象里 serialVersionUID 字段是做什么用的.有或者没有,它们之间有差别吗?除了Eclipse里提示的那个黄色的警 ...