细说spring事务配置属性
一、spring事务配置
1、spring配置
在配置数据源的下方配置
<!-- 事务配置 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!--使用注解事务管理器(配置主服务器)-->
<tx:annotation-driven transaction-manager="transactionManager"/>
2、增加@Transactional注解
在需要的service实现层方法上加入这个注解
加注解时需要注意的问题:
1、在需要事务管理的地方加@Transactional注解。
2、@Transactional注解只能应用到 public 可见度的方法上。
如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错,但是这个被注解的方法将不会展示已配置的事务设置。
3、@Transactional注解使用在service实现层的方法上,别的地方出现都不正确。
在接口上使用 @Transactional 注解,只能当你设置了基于接口的代理时它才生效。因为注解是 不能继承 的,这就意味着如果正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装。
4、@Transactional的事务开启,是基于接口的或者是基于类的代理被创建。所以在同一个类中一个方法调用另一个方法是有事务的方法,事务是不会起作用的。
5、Spring使用声明式事务处理,默认情况下,如果被注解的数据库操作方法中发生了unchecked异常,所有的数据库操作将rollback;如果发生的异常是checked异常,默认情况下数据库操作还是会提交的。
二、spring事务属性
我们先分类把表格列出,然后再详细说明
属性名 | 类型 | 说明 |
propagation | 枚举org.springframework.transaction.annotation.Propagation的值 | 事务传播行为 |
isolation | 枚举org.springframework.transaction.annotation.Isolation的值 | 事务隔离级别 |
readOnly | boolean | 事务读写性 |
属性名 | 类型 | 说明 |
noRollbackFor | Class<? extends Throwable>[] |
一组异常类,遇到时不回滚。默认为{} |
noRollbackForClassName | Stirng[] |
一组异常类名,遇到时不回滚,默认为{} |
属性名 | 类型 | 说明 |
rollbackFor | Class<? extends Throwable>[] | 一组异常类,遇到时回滚 |
rollbackForClassName | Stirng[] | 一组异常类名,遇到时回滚 |
属性名 | 类型 | 说明 |
value | String | 可选的限定描述符,指定使用的事务管理器 |
timeout | int | 超时时间,以秒为单位 |
三、数据库事务的四大特性和出现的问题
⑴ 原子性(Atomicity)
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,这和前面两篇博客介绍事务的功能是一样的概念,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
⑵ 一致性(Consistency)
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
⑶ 隔离性(Isolation)
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。
⑷ 持久性(Durability)
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
例如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。
以上介绍完事务的四大特性(简称ACID),现在重点来说明下事务的隔离性,当多个线程都开启事务操作数据库中的数据时,数据库系统要能进行隔离操作,以保证各个线程获取数据的准确性,在介绍数据库提供的各种隔离级别之前,我们先看看如果不考虑事务的隔离性,会发生的几种问题:
1,脏读
脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。
当一个事务正在多次修改某个数据,而在这个事务中这多次的修改都还未提交,这时一个并发的事务来访问该数据,就会造成两个事务得到的数据不一致。例如:用户A向用户B转账100元,对应SQL命令如下
update account set money=money+100 where name=’B’; (此时A通知B) update account set money=money - 100 where name=’A’;
当只执行第一条SQL时,A通知B查看账户,B发现确实钱已到账(此时即发生了脏读),而之后无论第二条SQL是否执行,只要该事务不提交,则所有操作都将回滚,那么当B以后再次查看账户时就会发现钱其实并没有转。
2,不可重复读
不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。
例如事务T1在读取某一数据,而事务T2立马修改了这个数据并且提交事务给数据库,事务T1再次读取该数据就得到了不同的结果,发送了不可重复读。
不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。
在某些情况下,不可重复读并不是问题,比如我们多次查询某个数据当然以最后查询得到的结果为主。但在另一些情况下就有可能发生问题,例如对于同一个数据A和B依次查询就可能不同,A和B就可能打起来了……
3,虚读(幻读)
幻读是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。
幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
四、具体spring属性的设置
Isolation 属性一共支持五种事务设置,具体介绍如下:
DEFAULT 使用数据库设置的隔离级别 ( 默认 ) ,由 DBA 默认的设置来决定隔离级别 .
READ_UNCOMMITTED 会出现脏读、不可重复读、幻读 ( 隔离级别最低,并发性能高 )
READ_COMMITTED 会出现不可重复读、幻读问题(锁定正在读取的行)
REPEATABLE_READ 会出幻读(锁定所读取的所有行)
SERIALIZABLE 保证所有的情况不会发生(锁表)
不可重复读的重点是修改 :
同样的条件 , 你读取过的数据 , 再次读取出来发现值不一样了
幻读的重点在于新增或者删除
同样的条件 , 第 1 次和第 2 次读出来的记录数不一样
Spring在TransactionDefinition接口中定义这些属性
在TransactionDefinition接口中定义了五个不同的事务隔离级别
ISOLATION_DEFAULT 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.另外四个与JDBC的隔离级别相对应
ISOLATION_READ_UNCOMMITTED 这是事务最低的隔离级别,它充许别外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读
ISOLATION_READ_COMMITTED 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。
ISOLATION_REPEATABLE_READ 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。
ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读。
在TransactionDefinition接口中定义了七个事务传播行为。
PROPAGATION_REQUIRED 如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。
PROPAGATION_SUPPORTS 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。但是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少许不同。
PROPAGATION_MANDATORY 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。
PROPAGATION_REQUIRES_NEW 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。
PROPAGATION_NOT_SUPPORTED 总是非事务地执行,并挂起任何存在的事务。
PROPAGATION_NEVER 总是非事务地执行,如果存在一个活动事务,则抛出异常
PROPAGATION_NESTED如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行
五、总结(不想看上面的直接看总结)
我就知道有人会直接看这个地方,因为现在的年轻人太浮躁了。
好吧,其实我也是这样的人。下面才是重点。
在看这些配置信息之前,我都是直接写的@Transactional然后就没有然后了。
而且项目也算是中型的项目,也没有出现极高的并发,所以没有出现什么问题。
所以,如果项目比较小,而且并非并不高的情况下,如果要使用事务,就直接写个注解,然后简单的手动回滚或者异常回滚就可以了。
主要是考虑到懒癌和开发效率。
但是,但是,但是。在有高的并发的情况下,需要按照以下的步骤依次考虑。
第一步:这个服务需要做的操作有那些,你可以先不加入事务,然后把整个业务逻辑全部都写出来,看看做了哪些操作。
第二步:排列操作的顺序,这步很重要,很多人都会小看在服务层里面的操作顺序这个步骤,先减少库存还是先增加订单,步骤上面的顺序很容易导致最后出现数据的问题。这个也要根据具体的业务逻辑来判断,我这里也不能给出确定的说法,需要你仔细斟酌。
第三步:列出所有涉及的表,找到所有涉及的表之后,就看看这几张表,除了这个事务之外还是否有别的服务会对表的数据进行修改,是否有多个同时修改的情况,是否有别的服务正在读取你修改或者修改到一半的数据,总之要找到对于这张表会同时有可能产生的所有操作进行分析。
第四步:确定当前是否会出现脏读,不可重复读和幻读这三种情况,这步如果你第一次做,分析起来会比较复杂,做多了会好一些,只能仔细考虑。
第五步:根据第四第五步所给出的结论正式确定上面事物的属性,找上面的“具体spring属性的设置”然后进行设置。
第六步:考虑时间,尽可能的让事务的执行时间缩短,因为事务是非常消耗性能的,特别是SERIALIZABLE,最安全也就是最慢的。
第七步:测试,不测试就没办法确定任何事务的可行性。那么肯定有人会问,我怎么测试啊,并发这么高我怎么模拟啊。有几个巧妙的方法,比如断点,让断点一直卡住,不执行然后同时去修改数据库以达到同时操作的目的;睡眠延时,导致事务执行时间变长让其容易导致并发;定时或者循环调用;测试接口的软件模拟并发。
总之做完以上的步骤,对于事务的考虑你肯定是已经明确了,如果再出现问题你也能解决了。
最后还有一句,事务确实是好用,但是并发的解决并不都只是通过事务来解决的,还有很多的中间件或者nosql来解决相应的并发。
参考博客:
http://www.cnblogs.com/fjdingsd/p/5273008.html
http://blog.csdn.net/bejustice/article/details/48245741?ref=myread