数据库事务(ACID) :
原子性(Atomicity): 事务是不可分割的最小操作单位,要么同时成功,要么同时失败
一致性(Consistency) :事务完成时,必须使所有的数据都保持一致状态 (就是上面案例, 不管事务执行是否成功, 总额不会变化)
隔离性(Isolation) :多个事务之间,操作的可见性 (将来有多个事务可以操作同一批数据 , 隔离性越强操作越不可见, 性能越低 . 隔离性越弱操作是可见的, 性能越高 一般不改变, 默认就可以, 默认的就是在mySQL里面当别人提交了或者回滚了, 我才能看到对方对应的修改)
持久性(Durability) :事务一旦提交或回滚,它对数据库中的数据的改变就是永久的
- 创建账户表 CREATE TABLE account(id INT PRIMARY KEY auto_increment,name VARCHAR(10),money DOUBLE(10,2)); -- 添加数据 INSERT INTO account(name,money) VALUES ('张三',1000),('李四',1000); SELECT * FROM account; -- 转账操作 -- 开启事务 BEGIN; -- 1.查询李四金额 -- 2.李四金额 -500 UPDATE account SET money = money -500 WHERE name = '李四'; -- 出错了.... -- 3.张三金额 + 500 UPDATE account SET money = money +500 WHERE name = '张三'; -- 提交事务 提交事务后数据就会持久化修改 COMMIT; -- 回滚事务 回滚到事务开启的状态, 不做任何修改 ROLLBACK;
== 说明:==
mysql中事务是自动提交的。
也就是说我们不添加事务执行sql语句,语句执行完毕会自动的提交事务。
可以通过这个语句查询默认提交方式:
SELECT @@autocommit;
查询到的结果是1 则表示自动提交,数据会自动持久化数据库, 结果是0表示手动提交 , 如果不手动commit只会临时修改, 不会持久化数据。当然也可以通过下面语句修改提交方式
set @@autocommit = 0;
注意 : oracle数据库是要手动提交的
Spring AOP事务管理
事务作用:在数据层保障一系列的数据库操作同成功同失败
Spring事务作用:在数据层或==业务层==保障一系列的数据库操作同成功同失败
数据层有事务我们可以理解,为什么业务层也需要处理事务呢?
因为业务层实际上组合了若干数据层功能, 在业务层开事务, 可以保障调用多个的数据层操作处于同一个事务, 同成功同失败 , 这就是Spring事务的作用 , 它是为了保障组合功能过程中事务的生效的.
Spring为了管理事务,提供了一个平台事务管理器
PlatformTransactionManager 接口
commit是用来提交事务,rollback是用来回滚事务。
PlatformTransactionManager只是一个接口,Spring还为其提供了一个具体的实现:
从名称上可以看出,我们只需要给它一个DataSource对象,它就可以帮你去在业务层管理事务。其内部采用的是JDBC的事务。所以说如果你持久层采用的是JDBC相关的技术,就可以采用这个事务管理器来管理你的事务。而Mybatis内部采用的就是JDBC的事务,所以后期我们Spring整合Mybatis就采用的这个DataSourceTransactionManager事务管理器。
转账案例-需求分析
接下来通过一个案例来学习下Spring是如何来管理事务的。
先来分析下需求:
需求: 实现任意两个账户间转账操作
需求微缩: A账户减钱,B账户加钱
为了实现上述的业务需求,我们可以按照下面步骤来实现下: ①:数据层提供基础操作,指定账户减钱(outMoney),指定账户加钱(inMoney)
②:业务层提供转账操作(transfer),调用减钱与加钱的操作
③:提供2个账号和操作金额执行转账操作
④:基于Spring整合MyBatis环境搭建上述操作
转账案例-环境搭建
步骤1:准备数据库表
create database spring_db character set utf8; use spring_db; create table tbl_account( id int primary key auto_increment, name varchar(35), money double ); insert into tbl_account values(1,'Tom',1000); insert into tbl_account values(2,'Jerry',1000);
步骤2:创建项目导入jar包 项目的pom.xml添加相关依赖
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.16</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.0</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.10.RELEASE</version> </dependency> </dependencies>
事务管理
上述环境,运行单元测试类,会执行转账操作,
Tom
的账户会减少100,Jerry
的账户会加100。这是正常情况下的运行结果,但是如果在转账的过程中出现了异常,如:
![]()
这个时候就模拟了转账过程中出现异常的情况,正确的操作应该是转账出问题了,
Tom
应该还是900,Jerry
应该还是1100,但是真正运行后会发现,并没有像我们想象的那样,Tom
账户为800而Jerry
还是1100,100块钱凭空消息了,银行乐疯了。如果把转账换个顺序,银行就该哭了。不管哪种情况,都是不允许出现的,对刚才的结果我们做一个分析:
①:程序正常执行时,账户金额A减B加,没有问题
②:程序出现异常后,转账失败,但是异常之前操作成功,异常之后操作失败,整体业务失败
当程序出问题后,我们需要让事务进行回滚,而且这个事务应该是加在业务层上,而Spring的事务管理就是用来解决这类问题的。
Spring事务管理具体的实现步骤为:
步骤1:在需要被事务管理的方法上添加注解
步骤2:在JdbcConfig类中配置事务管理器
步骤3:开启事务注解 在SpringConfig的配置类中开启
步骤4:运行测试类
会发现在转换的业务出现错误后,事务就可以控制回滚,保证数据的正确性。
知识点1:@EnableTransactionManagement
名称 @EnableTransactionManagement 类型 配置类注解 位置 配置类定义上方 作用 设置当前Spring环境中开启注解式事务支持 知识点2:@Transactional
名称 @Transactional 类型 接口注解 类注解 方法注解 位置 业务层接口上方 业务层实现类上方 业务方法上方 作用 为当前业务层方法添加事务(如果设置在类或接口上方则类或接口中所有方法均添加事务) Spring事务角色
重点要理解两个概念,分别是
事务管理员
和事务协调员
。
事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法
事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法
==注意:== 目前的事务管理是基于
DataSourceTransactionManager
和SqlSessionFactoryBean
使用的是同一个数据源。
未开启Spring事务之前:
开启Spring的事务管理后
Spring事务属性
在这一节中,我们主要学习三部分内容
事务配置
、转账业务追加日志
、事务传播行为
。一 : 事务配置
上面这些属性都可以在
@Transactional
注解的参数上进行设置。
readOnly:true只读事务,false读写事务,增删改要设为false,查询设为true。
timeout:设置超时时间单位秒,在多长时间之内事务没有提交成功就自动回滚,-1表示不设置超时时间。
rollbackFor:当出现指定异常进行事务回滚
noRollbackFor:当出现指定异常不进行事务回滚
思考:出现异常事务会自动回滚,这个是我们之前就已经知道的
noRollbackFor是设定对于指定的异常不回滚,这个好理解
rollbackFor是指定回滚异常,对于异常事务不应该都回滚么,为什么还要指定?
这块需要更正一个知识点,并不是所有的异常都会回滚事务,比如下面的代码就不会回滚
出现这个问题的原因是,Spring的事务只会对
Error异常
和RuntimeException异常
及其子类进行事务回顾,其他的异常类型是不会回滚的,对应IOException不符合上述条件所以不回滚
此时就可以使用rollbackFor属性来设置出现IOException异常进行回滚
![]()
rollbackForClassName等同于rollbackFor,只不过属性为异常的类全名字符串
noRollbackForClassName等同于noRollbackFor,只不过属性为异常的类全名字符串
isolation设置事务的隔离级别
DEFAULT :默认隔离级别, 会采用数据库的隔离级别
READ_UNCOMMITTED : 读未提交
READ_COMMITTED : 读已提交
REPEATABLE_READ : 重复读取
SERIALIZABLE: 串行化
介绍完上述属性后,还有最后一个事务的传播行为,为了讲解该属性的设置,我们需要完成下面的案例。
二 : 转账业务追加日志案例
事务传播行为