数据库事物
一、什么是事务
1.1 事务的概念
事务
是数据库管理系统的最小逻辑工作单元,这个逻辑工作单元包含一系列操作;这些操作作为一个整体一起向数据库系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合(工作逻辑单元);
通俗的讲,就是为了达到某个目的,而做的一系列的操作,要么一起成功(事务提交),要么一起失败(事务回滚)。
最常见的例子就是转账:
【举例】
小明给如花转账:
开启事务(begin;)-------
- 从小明的账户扣除1000块
- 给如花的账户增加1000块
事务提交(commit;)-------
上面例子在任务开启后一旦出现问题,都会导致事务回滚。
1.2 事务的四大特性
一原持久隔离
1.原子性(Atomicity):同一个事务中的多个操作是不可分割的原子单位。事务中所有操作要么全部执行成功,要么全部执行失败。
2.一致性(Consistency):事务提交后,数据库状态与其它业务规则保持一致。如转账业务,无论事务执行成功与否,参与转账的两个账号余额之和应该是不变的。
3.隔离性(Isolation):隔离性是指在并发操作中,不同事务之间应该隔离开来,使每个并发中的事务不会相互干扰。
4.持久性(Durability):一旦事务提交成功,事务中所有的数据操作都必须被持久化到数据库中,即使提交事务后,数据库马上崩溃,在数据库重启时,也必须你能保证通过某种机制恢复数据。
1.3 原生的JDBC事务处理
try {
connection.setAutoCommit(false);
// 数据库操作... insert / update / delete
connection.commit();
} catch (Exception ex) {
connection.rollback();
} finally {
connection.setAutoCommit(true);
}
二、事务的隔离级别
不同的事务隔离级别,在并发环境会存在不同的并发问题
√:可能出现的情况 ×:不会出现该情况 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
SERIALIZABLE(读序列化) | × | × | × |
REPEATABLE READ(可重复读)(mysql默认) |
× | × | √ |
READ COMMITTED(读已提交)(oracle,sql server默认) |
× | √ | √ |
READ UNCOMMITTED(读未提交) | √(无法容忍) |
√ | √ |
串行:我的事务尚未提交,别人就别想改数据。
可重复读:别人改数据的事务已经提交,我在我的事务中也不去读。
读已提交:别人改数据的事务已经提交,我在我的事务中才能读到。
读未提交:别人改数据的事务尚未提交,我在我的事务中也能读到。
这4种隔离级别,并行性能依次增高,安全性依次降低。
- 查看mysql当前隔离级别
SHOW VARIABLES;
SHOW VARIABLES LIKE "%transaction%";
-- 当前会话
select @@session.transaction_isolation;
select @@session.tx_isolation; -- 不常用
-- 全局
select @@global.transaction_isolation;
select @@global.tx_isolation; -- 不常用
- 设置隔离级别
-- 当前会话
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- 全局
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE;
2.1 现象演示
脏读
事务A读取了事务B中尚未提交的数据。如果事务B回滚,则A读取使用了错误的数据称为脏读
。
如花和小明两个事务,当前小明5000,如花3000,小明事务中小明给如花转账1000,此时,小明4000,如花4000,如花事务中如花查询余额4000,并提交事务,但是,小明事务还未提交,中途因为某些原因,给回滚了,这时,如花事务读到的就是错误数据,程序中这种情况是无法容忍的
。
脏读:指事务A正在访问数据,并且对数据进行了修改(事务未提交),这时,事务B也使用这个数据,后来事务A撤销回滚,并把修改后的数据恢复原值,B读到的数据就与数据库中的数据不一致,即B读到的数据是脏数据。
不可重复读
刚开始如花事务,查询余额100元,随后小明事务花掉了50元并提交,然后如花事务再次查询余额变为了50元然后提交事务。特指更新操作
。
不可重复读:在一个事务内,多次读取同一个数据,但是由于另一个事务在此期间对这个数据做了修改并提交,导致前后读取到的数据不一致;
幻读
在事务A多次读取构成中,事务B对数据进行了新增操作,导致事务A多次读取的数据行数不一致。
幻读:在一个事务中,先后两次进行读取相同的数据(一般是范围查询),但由于另一个事务新增或者删除了数据,导致前后两次结果不一致。
① 不可重复读和幻读的区别:
不可重复读侧重于读取到其他事务修改的数据,幻读侧重于读取到其他事务新增或者删除的数据。
② 可以采用锁机制来解决不可重复读和幻读:
对于不可重复读,只需对操作的数据添加行级锁,防止操作的数据发生变化;而对于幻读,需要添加表级锁,将整张表锁定,防止新增或者删除数据。
2.2 事务丢失演示
回滚丢失
比如A和B同时在执行一个数据
,然后B事务已经提交了,然后A事务回滚了,这样B事务的操作就因A事务回滚而丢失了。
现余额100元,小明事务花掉50元,这时如花事务放了50元并提交,而小明事务,因为某些原因回滚到了原来的100元,导致如花事务直接给丢失了。违反一致性,如花事物放进去的50元凭空消失了,是无法容忍的
提交覆盖丢失演示
A和B一起执行一个数据
,两个同时取到一个数据,然后,B事务首先提交,但是,A事务接下来又提交,这样就覆盖了B事务。
小明事务开始时账户是100元,小明准备放50元,随后如花事务很快的放了50元并提交,此时存钱罐是150元,这时小明事务还未结束(不知道余额被修改了)就提交事务150,结果导致原本应该是200元的最后缺变成了150元,如花事务被覆盖。
三、MVCC
一、前言
本文使用的mysql版本为5.7
1、MVCC是什么
MVCC
,全称Multi-Version Concurrency Control
,即多版本并发控制。MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。百度百科解释
多版本控制:指的是一种提高并发的技术。最早的数据库系统,只有读读之间可以并发,读写,写读,写写都要阻塞。引入多版本之后,只有写写之间相互阻塞,其他三种操作都可以并行,这样大幅度提高了InnoDB的可并发程度。在内部实现中,与Postgres在数据行上实现多版本不同,InnoDB是在 undo log 中实现的,通过 undo log 可以找回数据的历史版本。找回的数据历史版本可以提供给用户读(按照隔离级别的定义,有些读请求只能看到比较老的数据版本),也可以在回滚的时候覆盖数据页上的数据。在 InnoDB 内部中,会记录一个全局的活跃读写事务操作记录链表
,其主要用来判断事务的可见性。
MVCC 在 InnoDB 中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读
2、当前读与快照读
在学习MVCC多版本并发控制之前,必须先了解一下,什么是MySQL InnoDB 下的当前读
和快照读
- 当前读
像 select lock in share mode(共享锁
), select for update ; update, insert ,delete(排他锁
)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁
- 快照读
像不加锁
的select操作就是快照读,即不加锁的非阻塞读。快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读。之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以将 MVCC 认为是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销。既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本
说白了MVCC就是为了实现读-写冲突不加锁,而这个读指的就是
快照读
, 而非当前读,当前读实际上是一种加锁的操作,是悲观锁的实现
3、当前读与快照读和MVCC的关系
- 准确的说,MVCC 多版本并发控制指的是 “维持一个数据的多个版本,使得读写操作没有冲突” 这么一个概念。仅仅是一个理想概念
- 而在MySQL中,实现这么一个 MVCC 理想概念,就需要MySQL提供具体的功能去实现它,而快照读就是MySQL为实现 MVCC 理想模型的其中一个具体非阻塞读功能。而相对而言,当前读就是悲观锁的具体功能实现
- 要说的再细致一些,快照读本身也是一个抽象概念,再深入研究。MVCC模型在MySQL中的具体实现则是由
3个隐式字段
,undo log
,Read View
等去完成的,具体可以看下面的MVCC实现原理
4、MVCC解决的问题
数据库并发场景有三种,分别为:
读-读
:不存在任何问题,也不需要并发控制读-写
:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读写-写
:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失
备注:
第1类丢失更新:事务A撤销时,把已经提交的事务B的更新数据覆盖了
第2类丢失更新:事务A覆盖事务B已经提交的数据,造成事务B所做的操作丢失
5、MVCC带来的好处
多版本并发控制(MVCC)是一种用来解决读-写冲突
的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。 所以MVCC可以为数据库解决以下问题
- 在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能
- 同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题
6、小结
总之,MVCC就是因为大牛们,不满意只让数据库采用悲观锁这样性能不佳的形式去解决读-写冲突问题,而提出的解决方案,所以在数据库中,因为有了MVCC,所以可以形成两个组合:
MVCC + 悲观锁
MVCC解决读写冲突,悲观锁解决写写冲突MVCC + 乐观锁
MVCC解决读写冲突,乐观锁解决读写冲突
这种组合的方式就可以最大程度的提高数据库并发性能,并解决读写冲突,和写写冲突导致的问题
二、事务隔离性的实现原理
为了实现事务隔离,数据库延伸出了数据库锁,其中Innodb事务的隔离级别是由锁机制和MVVC(多版本并发控制)实现的
1、Mysql锁机制
MySQL锁机制的基本工作原理就是:事务在修改数据库之前,需要先获得相应的锁,获得锁的事务才可以修改数据;在该事务操作期间,这部分的数据是锁定,其他事务如果需要修改数据,需要等待当前事务提交或回滚后释放锁。(锁机制的更多说明可以参考另一篇博客:MySQL数据库:锁机制)
通过对InnoDB不同锁类型的特性分析,可以利用锁解决脏读、不可重复读、幻读:
- 排它锁解决脏读:在读已提交的隔离级别下,事务A只有在对数据修改时才加排它锁,但直到事务 commit 时才释放锁。因此,同时进行的事务B希望读取同一行数据时,会被事务A的排它锁堵塞,所以解决了脏读的问题
- 共享锁解决不可重复读:在可重复读的隔离级别下,除了执行读已提交的排它锁方式,还会在读取一行数据时,为这行数据添加共享锁直至事务 commit。例如,事务A读取ID=1这一行数据,然后为ID=1添加共享锁,事务B同时希望update ID=1,此时获取写锁失败,因此在事务A执行完之前,没有其他任何事务可以对ID=1这一行做修改,因此解决了重复读的问题
- 临键锁解决幻读
虽然共享锁和排它锁解决了事务隔离的并发问题,但锁会导致大量的堵塞,性能下降。某些时候会造成死锁,为了解决死锁,还要添加死锁探测机制,性能进一步下降,因此需要更高效的方式实现事务的隔离级别,也就是 MVCC 多版本并发控制。
2、MVCC
InnoDB 的 MVCC 的实现思路是:对每一行数据用 undo log 记录多个版本,每个版本的数据可能都不相同,然后根据 事务ID 去寻找适合它的版本数据,从而实现不同事务之间的隔离性。MVCC 具体实现实现方式是在每行记录后面保存两个隐藏的列:
- DB_TRX_ID:标识当前数据属于哪个事务,每次提交事务,事务ID会自增,事务开始时会把该事务ID放到当前事务影响的行事务ID字段中
- DB_ROLL_PTR:指向该行数据 undo log 的指针,undo log 日志文件保存了该行记录的所有版本数据,并在日志中通过链表形式组织
通过 MVCC,数据库可以使得事务的读取不需要很多读锁,提升了数据库的性能。当一个事务完成时,数据库会删除关于这条事务所有的 undo log,若未完成事务数据库崩溃则根据 undo log回滚,实现原子性。
MVCC只在 可重复度 和 读已提交 两个隔离级别下才会工作,其中,MVCC实质就是通过保存数据在某个时间点的快照来实现的。
- 读已提交:事务A执行每一条语句时,生成一份活跃事务表,根据这份表去获取数据,因此不会获取到脏数据(未提交事务引起的),但会有重复读问题(因为可以读取到commit 的事务数据)
- 可重复读:事务A只有在事务开始时才生成一份活跃事务表,因此不会读取到事务A执行中 commit 的其它事务引起的数据变更,也就不存在重复读问题。
在并发访问数据库时,对正在事务中的数据做MVCC多版本的管理,以避免写操作阻塞读操作,并且可以通过比较版本解决快照读方式的幻读问题,但对于当前读的幻读,MVCC并不能解决,需要通过临键锁来解决。
- 快照读:Innodb快照读,数据的读取将由 cache(原本数据) + undo(事务修改前的数据) 两部分组成
- 当前读:SQL读取的数据是最新版本。通过锁机制来保证读取的数据无法通过其他事务进行修改
在可重复读的隔离级别下,MVCC具体操作:
(1)SELECT操作:InnoDB遵循以下两个规则:
只查找数据行的事务ID小于或等于当前事务ID的版本,这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的记录。
行的删除版本要未被定义,读取到事务开始之前状态的版本,这可以确保事务读取到的行,在事务开始之前未被删除。只有同时满足的两者的记录,才能返回作为查询结果。
(2)INSERT:InnoDB为新插入的每一行保存当前事务编号作为行版本号。(3)DELETE:InnoDB为删除的每一行保存当前事务编号作为行删除标识。
(4)UPDATE:InnoDB为插入一行新记录,保存当前事务编号作为行版本号,同时保存当前事务编号到原来的行作为行删除标识。
保存这两个额外系统版本号,使大多数读操作都可以不用加锁。这样设计使得读数据操作很简单,性能很好,并且也能保证只会读取到符合标准的行,不足之处是每行记录都需要额外的存储空间,需要做更多的行检查工作,以及一些额外的维护工作。
关于MVCC更多详细的内容,推荐读者阅读这篇文章:《聊聊MVCC》
3、持久性
持久性的实现关键在于redo log日志,
在执行SQL时会保存已执行的SQL语句到一个指定的Log文件,当执行recovery
时重新执行redo log
记录的SQL操作。
- redo log日志
当向数据库写入数据时,执行过程会首先写入Buffer Pool,Buffer Pool中修改的数据会定期刷新到磁盘中(这一过程叫做刷盘),这整一过程称为redo log。
redo log 分为:
- Buffer Pool内存中的日志缓冲(redo log buffer),该部分日志是易失性的
- 磁盘上的重做日志文件(redo log file),该部分日志是持久的
Buffer Pool的使用可以大大提高了读写数据的效率,但是也带了新的问题:如果MySQL宕机,而此时Buffer Pool中修改的数据在内存还没有刷新到磁盘,就会导致数据的丢失,事务的持久性无法保证。
为了确保事务的持久性,在当事务提交时,会调用fsync接口对redo log进行刷盘, (即redo log buffer写日志到磁盘的redo log file中 ),刷新频率由 innodb_flush_log_at_trx_commit变量来控制的:
0: 每秒刷新缓冲池中的数据写入到磁盘中的,当系统崩溃,会丢失1秒钟的数据 ;
1: 事务每次提交的时候,就把缓冲池中的数据刷新到磁盘中;
2:提交事务的时候,把缓冲池中的数据写入磁盘文件对应的 os cache 缓存里去,而不是直接进入磁盘文件。可能 1 秒后才会把 os cache 里的数据写入到磁盘文件里去。
4、一致性
一致性指的是事务不能破坏数据的完整性和业务的一致性 :
- 数据的完整性: 实体完整性、列完整性(如字段的类型、大小、长度要符合要求)、外键约束等
- 业务的一致性:例如在银行转账时,不管事务成功还是失败,双方钱的总额不变。
那是如何保证数据一致性的?其实数据一致性是通过事务的原子性、持久性和隔离性来保证的:
原子性:语句要么全执行,要么全不执行,是事务最核心的特性,事务本身就是以原子性来定义的;主要基于undo log实现
持久性:保证事务提交后不会因为宕机等原因导致数据丢失;主要基于redo log实现
隔离性:保证事务执行尽可能不受其他事务影响;InnoDB默认的隔离级别是RR,RR的实现主要基于锁机制(包含next-key lock)、MVCC(包括数据的隐藏列、基于undo log的版本链、ReadView)
四、spring事务的传播特性
4.1 传播特性
4.1.1 什么是传播特性?
指当一个事务方法B被另一个事务方法A调用时,这个事务方法B应该如何进行。
【举例】
思考:小弟出现异常,老大要不要回滚?
4.1.2 Spring提供的7种事务传播特性
- propagation_required: 默认事务类型,如果没有,就新建一个事务;如果有,就加入当前事务。
适合绝大多数情况。
- propagation_required_new: 如果没有,就新建一个事务;如果有,就将当前事务挂起。
- propagation_nested: 如果没有,就新建一个事务;如果有,就在当前事务中嵌套其他事务。
- propagation_supports: 如果没有,就以非事务方式执行;如果有,就使用当前事务。
- propagation_not_supported: 如果没有,就以非事务方式执行;如果有,就将当前事务挂起。即无论如何不支持事务。
- propagation_never: 如果没有,就以非事务方式执行;如果有,就抛出异常。
- propagation_mandatory: 如果没有,就抛出异常;如果有,就使用当前事务。
总结:
【1】死活不要事务的
propagation_never: 没有就非事务执行,有就抛出异常。
propagation_not_supported: 没有就非事务执行,有就直接挂起,然后非事务执行。
【2】可有可无的
propagation_supports: 有就用,没有就算了
【3】必须有事务的
propagation_requires_new: 有没有都新建事务,如果原来有,就将原来的挂起。里面的事务,和外面事务是完全隔离的,互不影响的,外面事务不影响里面的事务,里面的事务也不影响外面的事务。
propagation_nested: 如果没有,就新建一个事务,如果有,就在当前事务中嵌套其他事务。外面的事务回滚的时候会影响里面的事务,但是,里面的事务不影响外面的事务。
propagation_requered: 如果没有,就新建一个事务,如果有,就加入当前事务。
propagation_mandatory: 如果没有,就抛出异常,如果有,就使用当前事务。
4.2 七种事物代码举例说明
数据库测试数据初始化:Tom 1000、jerry 1000
4.2.1 propagation_never
1、如果没有,就以非事务方式执行;如果有,就抛出异常。
2、假如有事务的话,老大也报错,小弟也报错,数据一定会回滚为最初的状态。
3、从上方数据库结果来看,转账成功了,出现异常缺没有回滚,说明两个方法都没有事务处理。
4、假如没有配置propagation.never,同时老大有事务,老大调用小弟,那老大和小弟就是在同一个事务中运行的;
而现在的目的就是不想让小弟在事务中运行,所以需要为小弟添加如下配置。
5、假如,现在小弟配置了propagation.never事物传播特性,而老大又有事务,就会直接抛出异常。
4.2.2 propagation_not_supported
1、没有就非事务执行,有就直接挂起,然后非事务执行。
2、出异常后
老大,值没有变化,说明有事物并且因为执行异常回滚了
小弟,因为使用porpagation.not_supported事物传播特性,导致没有事务,所以,出错后没有回滚。
4.2.3 propagation_supports
1、有就用,没有就算了
2、propagation.supports有事务,就用,没有事务就算了。目前,老大有事务,没有报错,小弟配置了supports事物传播类型,那它俩应该在同一个事务里面,小弟报错,老大也跟着回滚。
如上图所示老大,小弟,都回滚了,老大,没有减100,小弟,也没有加100。
3、如上图所示,老大没有事务,小弟也没有事务,报错后,都没有回滚。
4.2.4 propagation_requires_new
1、有没有都新建事务,如果原来有,就将原来的挂起。
2、由上图结果分析,老大没有事务,报错后,没有回滚,而小弟,新建了一个事务,报错后,回滚了。
3、老大,加了事务,没有报错,所以,没有回滚,成功减了100.
4、小弟,新建了一个单独的事务,报错了,所以回滚了,没有加100,还是1000。两个事务互不影响。
老大有事务,出了问题回滚了,所以还是1000
小弟也有事务,但跟老大不是一个事务,它也没有出错,所以,加了100。
4.2.5 propagation_nested
单向传递回滚操作,外部异常导致的回滚会连带内部事物一起回滚。
相反,如果内部产生异常发生回滚不会导致外部发生回滚
1、如果没有,就新建一个事务,如果有,就在当前事务中嵌套其他事务。
2、老大,有事务,没有出现问题,老大成功减100。小弟,也有事务,但出现了问题,所以回滚了,还是1000,不影响老大的事务。
老大出异常,老大和小弟,都回滚了。
4.2.6 propagation_requered
1、如果没有,就新建一个事务,如果有,就加入当前事务。
都没有改变,小弟出异常,由于是同一个事务,所以,老大、小弟都回滚了。老大出异常,小弟无异常,同理,也都会回滚。
4.2.7 propagation_mandatory
如果没有,就抛出异常,如果有,就使用当前事务。
老大没有事务,直接抛出异常
五、spring配置事务
5.1 基础代码
5.1.1 实体类
@Data
public class User {
private Integer id;
private String name;
private Integer money;
}
5.1.2 Dao
@Repository
public class UserDAO {
@Autowired
private JdbcTemplate jdbcTemplate;
// 转出
public void out(String fromName, Integer money) {
String sql = "update user set money = money-? where name =? ";
jdbcTemplate.update(sql, money,fromName);
}
// 转入
public void in(String toName, Integer money) {
String sql ="update user set money = money+? where name =?";
jdbcTemplate.update(sql,money,toName);
}
}
5.1.3 service
@Service
public class UserService {
@Autowired
private UserDAO userDAO;
// 没有事物的转账业务
public void transfer(String fromName, String toName, Integer money) {
userDAO.out(fromName, money); // 出钱
int x = 10;
if(x == 10) {
throw new RuntimeException("出错啦!");
}
userDAO.in(toName, money); // 入钱
}
}
5.1.4 xml文件配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache-3.1.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<!--数据源-->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://192.168.75.131:3306/spring-dk"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!-- 模板方法 -->
<bean class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- bean扫描配置 -->
<context:component-scan base-package="com.dk"/>
</beans>
5.1.5 测试
@Test
public void T1(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService bean = ac.getBean(UserService.class);
bean.transfer("tom","jerry",100);
}
@Test
public void T2(){
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
UserService bean = ac.getBean(UserService.class);
bean.transfer("tom","jerry",100);
}
5.2 编程式事务
XML方式配置事务:在applicationContext.xml中添加事务管理器和事务管理器模板的配置
<!-- 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 事务管理的模板 -->
<bean id="transactionTemplate"
class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager" />
</bean>
注解方式配置:事务管理器和事务管理器模版
@ComponentScan("com.dk")
public class AppConfig {
@Bean
public DataSource dataSource(){
BasicDataSource ds = new BasicDataSource();
ds.setUrl("jdbc:mysql://192.168.75.131:3306/spring-dk");
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUsername("root");
ds.setPassword("root");
return ds;
}
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager tm = new DataSourceTransactionManager();
tm.setDataSource(dataSource);
return tm;
}
@Bean
public TransactionTemplate transactionTemplate(DataSourceTransactionManager
transactionManager){
TransactionTemplate transactionTemplate = new TransactionTemplate();
transactionTemplate.setTransactionManager(transactionManager);
return transactionTemplate;
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
}
修改UserService,使用编程式事务完成事务管理:
@Service
public class UserService {
@Autowired
private UserDAO userDAO;
@Autowired
private TransactionTemplate transactionTemplate;
// 编程式事务 转账业务
public void transfer(String fromName, String toName, Integer money) {
transactionTemplate.execute(status -> {
userDAO.out(fromName, money);// 转出钱
int x = 10;
if(x==10){
throw new RuntimeException("出错啦!!");
}
userDAO.in(toName, money);// 收入钱
return null;
});
}
}
再测试:
@Test
public void T1(){
ApplicationContext ac = new
ClassPathXmlApplicationContext("applicationContext.xml");
UserService bean = ac.getBean(UserService.class);
bean.transfer("tom","jerry",100);
}
@Test
public void T2(){
AnnotationConfigApplicationContext ac = new
AnnotationConfigApplicationContext(AppConfig.class);
UserService bean = ac.getBean(UserService.class);
bean.transfer("tom","jerry",100);
}
5.3 声明值事务-基于AspectJ XML方式
注:基于TransactionProxyFactoryBean代理的方式是比较古老的方式,在这里就不赘述了。
基于XML方式的配置:
删除applicationContext.xml中的事务管理模版的配置,就是删除下面的配置:
<!-- 事务管理的模板 -->
<bean id="transactionTemplate"
class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager" />
</bean>
添加事务定义的配置和AOP的配置:
<!--基于AspectJ 申明式事务XML配置方式-->
<!-- 定义一个增强 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 增强(事务)的属性的配置 -->
<tx:attributes>
<!--
isolation:DEFAULT ,事务的隔离级别。
propagation:事务的传播行为.
read-only:false,不是只读
timeout:-1
no-rollback-for:发生哪些异常不回滚
rollback-for:发生哪些异常回滚事务
-->
<tx:method name="transfer" isolation="DEFAULT" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- aop配置定义切面和切点的信息 -->
<aop:config>
<!-- 定义切点:哪些类的哪些方法应用增强 -->
<aop:pointcut expression="execution(* com.dk.beans.service..*.*(..))" id="mypointcut" />
<!-- 定义切面: -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="mypointcut" />
</aop:config>
将业务类的方法改回原来的方式:
删除UserService中的TransactionTemplate声明如下所示
@Service
public class UserService {
@Autowired
private UserDAO userDAO;
// 没有事物的转账业务
public void transfer(String fromName, String toName, Integer money) {
userDAO.out(fromName, money); // 出钱
int x = 10;
if(x == 10) {
throw new RuntimeException("出错啦!");
}
userDAO.in(toName, money); // 入钱
}
}
测试:
...
基于注解的声明式事务:
在配置类上配置@EnableTransactionManagement开启事务。删除注解类中和事务相关的@Bane
@ComponentScan("com.dk")
@EnableTransactionManagement
public class AppConfig {
@Bean
public DataSource dataSource(){
BasicDataSource dataSource = new BasicDataSource();
dataSource.setUrl("jdbc:mysql://192.168.75.131:3306/spring-dk");
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new
DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
}
在UserService类上方或者方法上方通过@Transactional完成事务配置:
@Service
@Transactional
public class UserService {
}
· 分享4款.NET开源、免费、实用的商城系统
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
2021-07-11 让MySql支持Emoji表情存储
2021-07-11 jdbcTemplete
2021-07-11 编码与解码的理解
2021-07-11 FTPClient下载文件遇到的坑
2021-07-11 FTP链接的主被动模式:500 Illegal PORT command的问题