作者说
首先感谢大家对我的支持,能从头一直看到现在,前面我们系统的对Spring的配置,IOC和AOP进行一个讲解,从这一讲开始我们将用三讲的时间为大家讲系统的讲解事务以及Spring中的事务。总体来说比较复杂,涵盖知识点比较多,我会慢慢的逐一为大家讲解。大家不要担心。我们先从事务这个概念开始讲解。
事务的概念
事务就是一系列的动作,它们被当做一个单独的工作单元,比如说对一张银行卡的存和取,其中存和取就是两个工作单元,这些动作要么全部完成,要么全部不起作用。例如小田用自己的银行卡给小张划过去三百块钱,小张的银行卡账户增加三百,这个时候数据库断电,小田的银行卡没有减三百,这就会凭空的让银行造成三百元的损失,这是万万不可的。事物的作用这时候就显现出来了,如果中途断电,刚才小张银行卡上的加三百元就会回退。两步操作全部没有执行,银行就不会有损失了。事务就是做这个用的。
事务的四大特性
原子性
原子性就像我刚才说的那样,原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
一致性
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
拿转账来说,假设小张和小田两者银行卡钱加起来一共是500,那么不管小张和小田之间如何转账,不管他们之间转了多少次,事务结束后两个用户的钱相加起来应该还得是500,这就是事务的一致性。
隔离性
隔离性对比其他几个特性要复杂得多,隔离性是什么,有点类似线程的同步,但要复杂得多,隔离性想达到一种效果,对于两个并发的操作T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。隔离性的理想化就是一个事务一个事务去做,但这样会大大影响执行效率,因此事务提供多种隔离级别,尽可能的在保证效率的同时保证事务的隔离性。
级别一 读未提交(Read uncommitted)
概念:一个事物可以读取到另一个未提交完成事务的数据
假如说我们的父亲正在给我们打一个月2000的生活费,但是父亲手抖打了20000,而恰巧我们在父亲给我们存钱这个事务还没有提交完全之前查看了自己的账户,开启了另一个事务,发现我们的户头是20000,我们很兴奋,这时候父亲也发现了问题,在存20000的这个事务还没有完成前取消了这个事务,事务回滚,20000元从我们的账户上消失。我们过一会再去看自己账户发现我们的两万元没得了。这就尴尬了。我们有一个名词来表示我们上述的情景,叫做脏读,我们看到的只是爸爸没有提交事务时的数据。而不是提交完成后的数据。而我们的解决办法就是将事物的级别调成读提交。
级别二 读提交(Read committed)
概念:一个设置了读提交的事务要等到另一个事务提交之后才能读取数据。
读提交解决了我们脏读的问题,若有事务对数据进行了更新的操作,设置了读提交的事务要等到这个更新操作事务提交后才能读取数据,但读提交也有自己的问题。
小田和小张开通了屈臣氏的亲密付功能。屈臣氏为小张和小田准备了两张一样的卡,两人可以一起花存在屈臣氏里的五百块钱。一天小田去屈臣氏买洗面奶,小田把卡给收银员收银员先是刷了一下卡,上面提示卡里有五百元,就在这个时候小张去另一家屈臣氏买面膜直接将卡里的五百元给刷掉了,小田的收银员看卡里有五百点击付款的按钮,这个事务首先是等小张那边的更新事务完成,因为我们设置了小田的事务是读提交,然后系统在进行第二次检测金额准备扣款的时候发现卡里的钱已经没了。这让小田很无奈。这个问题学名叫做不可重复读。小田每次读取到的数据都是不一样的。
这就是我们读提交的坏处。读提交的事务明明先占有的这张卡,却被另一个事务抢先更新了。而我们要解决这个问题,我们就要运用级别三的重复读。
级别三 重复读(Repeatable read)
概念:当一个事务开启的时候,不再允许有其他的修改事务对数据进行修改
由于我们的事务级别为重复读,顾名思义,你读多少遍都显示一样的数,因为我们这个事务在执行的时候是不允许有其他的更新事务。那么没有更新事务,我们的钱也就不会变了。刚才小田就可以理所当然的扣钱了从卡里,而不会有执行一半钱被别人花的问题了。
当然我们要注意这里重复读只会拦截其他更新事务,对于插入事务我们无法阻挡在外面,我们读的时候可以有插入操作进来。
举一个小例子,小张去查小田银行卡的使用记录,小张的查询事务开启,我们可以从ATM上看到什么时间什么时候小田花了多少钱,这时候小田用银行卡买了一台电脑。小张这边呢用ATM机把小田的对账单打出来了,但却意外的发现,跟刚才看到的金额以及记录对不上,仔细看才发现刚刚小田买了台电脑。例子中问题的学名叫做幻读。我们以为出现了幻觉。
那我们怎么解决幻读的问题呢,我们要将事务设置为级别最高的序列化。
级别四 序列化(Serializable)
概念:是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。
上述就是我们事务的隔离性的四个隔离级别,需要注意的是大多数数据库默认的事务隔离级别是Read committed,比如Sql Server , Oracle。MySQL的默认隔离级别是Repeatable read。我们在Spring中使用的隔离策略就是在mysql中使用的策略,如果我们不设置spring就默认使用mysql中的隔离策略,但如果我们设置了Spring的隔离策略,以spring为准。
持久性
持久性套用一句广告词,“使命必达”,这就意味着一个事务一旦被提交上去了,我们的数据库必需按照我们事务进行增删改查。当然这是在我们没有刻意的将事务回滚的前提下。哪怕是我们的数据库服务器出现了问题也没关系,事务会等着你,直到你完成我事务交给你的任务,我才算完成成功。这样就不会出现事务告诉用户我成功了,但是数据库因为故障而没有执行事务的错误。
在讲完上述概念之后,我们开始讲Spring是如何支持事务的。
事务实例搭建
Spring的事务是通过AOP来完成的,我们的开启事务相当于前置通知,提交事务相当于返回通知,回滚事务相当于异常通知,关闭事务则是一个后置通知。这是我们Spring来实现事务的底层流程出发点。
Spring可以通过声明式事务来管理,我们只需要像以前那样去写事务里面的代码,剩下的由Spring来替我们写。
我们来实现一个功能,我们新建三个数据库表。

book:书的名字和价钱
account:用户拥有的资金
book_stock:每本书的库存
我们写三个方法来完成我们的需求。我们的需求是一个用户买书,买书的步骤是先将对应书的库存减一,然后将我们用户的账户减去书的价格。

下面我们来写买书这个方法。

该方法执行了三步,先获取书的单价,然后更新我们的库存,最后我们更新用户金额。我们在Test来执行这个方法,我们将数据库里AA用户的金额调成70圆,1001这本书的价格为100,1001有十本库存,我们执行方法后,报异常,余额不足。由于余额不足,我们并没有执行从账户减100的操作,但我们发现我们1001这本书的库存变成了9本。这个问题是相当严重的。用户没有买成功但库存却在减少,想想是不是很可怕。而事务将会帮助我们解决这个问题。
总结
这一讲我们重点讲解的事务的四大特性以及布置好了我们的需求,我们下一讲将用Spring的事务来解决这个问题。
著作权声明
作者 田俊哲,首次发布于 Shmilyz Blog,本文原创,转载请保留以上链接!
-
Previous
解决问题系列之在springmvc中AJAX发送PUT请求Tomcat无法封装解决方案及源码分析 -
Next
教你写框架系列之Spring第七讲事务概念讲解与Spring事务配置(二)