Spring的事务管理
Posted lqgcn
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring的事务管理相关的知识,希望对你有一定的参考价值。
那么什么是事务呢?相信大家都知道事物是怎么一回事吧。为了防止有些人忘记了,现在我在简要的说下什么是事务。
事务其实就是一系列的动作, 它们被当做一个单独的工作单元. 这些动作要么全部完成, 要么全部不起作用。比如说我们在淘宝买东西扣钱的时候,这时候我们余额要减少同时库存也要减少,这两个操作要么都完成,要么都不完成。如果一个完成一个不完成,那这样要么用户少了钱要么库存少了件,这就是事务。
事务的四个关键属性(ACID)
原子性(atomicity): 事务是一个原子操作, 由一系列动作组成. 事务的原子性确保动作要么全部完成要么完全不起作用.
一致性(consistency): 一旦所有事务动作完成, 事务就被提交. 数据和资源就处于一种满足业务规则的一致性状态中.
隔离性(isolation): 可能有许多事务会同时处理相同的数据, 因此每个事物都应该与其他事务隔离开来, 防止数据损坏.
持久性(durability): 一旦事务完成, 无论发生什么系统错误, 它的结果都不应该受到影响. 通常情况下, 事务的结果被写到持久化存储器中.
那么在spring中是如何管理事物的呢?其实非常简单,用注解的方法只需要写几句话就OK了。那么我们来看一看把。
数据库文件和一些Dao什么的我就不贴出来了,参照我下面的例子应该非常容易理解。首先我们现在spring配置文件中配置事务管理器和启用事物的注解(需要导入tx命名空间)
1 <!-- 配置事务管理器 --> 2 <bean id = "transactionManager" 3 class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 4 <property name="dataSource" ref = "dataSource"></property> 5 </bean> 6 <!-- 启用事物注解 --> 7 <tx:annotation-driven transaction-manager="transactionManager"/>
随后在Service层中启用事务注解
1 package com.SpringTransaction; 2 3 import org.junit.rules.Timeout; 4 import org.springframework.beans.factory.annotation.Autowired; 5 import org.springframework.stereotype.Service; 6 import org.springframework.transaction.annotation.Isolation; 7 import org.springframework.transaction.annotation.Propagation; 8 import org.springframework.transaction.annotation.Transactional; 9 10 @Service("BookShopService") 11 public class BookShopServiceImpl implements BookShopService { 12 @Autowired 13 private BookShopDao bookShopDao; 14 /* 15 *添加事务注解(@Transactional) 16 *1.使用 propagation 指定事务的传播行为, 即当前的事务方法被另外一个事务方法调用时 17 *如何使用事务, 默认取值为 REQUIRED, 即使用调用方法的事务 18 *2.使用isolation来确定事务的隔离级别,最常用的取值为READ_COMMITTED 19 *3.noRollbackFor,通常情况下spring的声明式事物会对所有运行时的异常进行回滚,也可以通过noRollbackFor来 20 *设置不需要回滚的异常。通常情况下使用默认值就行了。 21 * @Transactional(propagation = Propagation.REQUIRED, 22 * isolation=Isolation.READ_COMMITTED, 23 * RollbackFor={UserAccountException.class}) 24 *4.readOnly:使用readOnly可以指定事务是否只读,若为true表示只读, 25 *这样可以帮助数据库引擎优化事务. 若真的事一个只读取数据库值的方法, 应设置 readOnly=true 26 *5.timeout指定强制回滚之前事务可以占用的时间. 防止一个访问长时间占用 27 * */ 28 @Transactional(propagation = Propagation.REQUIRED, 29 isolation=Isolation.READ_COMMITTED, 30 readOnly = false, 31 timeout=3) 32 public void purchase(String username, int bookid) { 33 //1.获取书的单价 34 int price = bookShopDao.findBookPriceByBookId(bookid); 35 //2.更新书的库存 36 bookShopDao.updateBookStock(bookid); 37 //3.更新用户余额 38 bookShopDao.updateUserAccount(username, price); 39 } 40 }
现在我来说说注解中propagation,isolation这两个比较复杂的属性,其他的看看我上面的注释应该就可以明白。
1.首先propagation是用来指定事务的传播行为的:其中比较常用的值为REQUIRED和REQUIRES_NEW。
REQUIRED:如果有一个事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行。
REQUIRES_NEW:当前方法必须启动新的事务,并在它自己的事务内运行,如果有事务在运行,就先将其挂起。
用个例子来说明两者的区别:一个订单有两本书A和B,A书价格为100,B本为60.账户余额有120元,购买A书有一个 方法,购买B书有一个方法。都有添加事务,同时,为这个订单付款的C方法中包含了上述两种方法。C方法也添加了个事务。如果C方法事务的传播行为是REQUIRED的话,A,B两个方法就会执行C的事务。最终两本书一本书都不会买用户余额也不会减少。如果C方法事务的传播行为是REQUIRES_NEW的话,A,B两个方法会执行自己的事务然后在执行C的事务,这样用户为订单付款的时候会把A书买了,B书提示余额不足。用户的余额也会减少100.
2.isolation是指事务的隔离级别,事务的隔离级别是用来避免在并发操作中出现脏读,不可重复读和幻读的问题。事务的隔离级别由低到高共有4种分别为Read uncommitted、Read committed、Repeatable read和 Serializable。下面我来说说这4种隔离级别代表者什么。
2.1.Read uncommitted:字面上意思,就是一个事务可以读取另一个未提交事务的数据。比如你老板要给你发工资的时候不小心多发了2000元,但是事务还没提交。就在这时你查看了自己这个月的工资,会发现比以往多了2000元。但是当你老板发现不对的时候马上进行回滚并提交事务,最终你的工资并没有增加。这就是脏读。那么如何解决脏读问题呢?这就需要使用.Read committed这个更高的事务隔离级别来解决。
2.2.Read committed:读提交,就是一个事务要等待另一个事务完成后才能读取数据。比如说你要用银行卡买一个东西是6000元,在你买单的时候收费系统首先检测你的卡里有1W元确实够买,但是如果在这个时候你女朋友用你的银行卡花了5000块买了套化妆品。这时当收费系统准备扣款的时候,再检测卡里的钱,发现没钱了(第二次检查自然是在你女朋友买化妆品的事务提交后才进行)。这时候你就很无语,难道刚刚看眼花了,刚刚卡里明明钱是够的。这就是脏读。当然事务的隔离级别设为Read committed的话就可以避免脏读的情况,但是这个事务隔离级别中,一个事务范围内两个相同的查询却返回不同的数据,这就是不可重复读,如要解决不可重复读的问题那就只能使用比Read committed更高的事务隔离级别Repeatable read。
2.3.Repeatable read:重复读,就是在开始读取数据时(开启事务时),不在允许修改操作。例子还是上面的例子如果事务的隔离级别设为Repeatable read,那么当你要花6000元买个东西的时候,事务便会开启,不再允许其他的事务执行UPDATE修改操作。当你女朋友要化5000元买化妆品的时候就不能完成付款。这样你就可以顺利的买下你要买的东西了(是不是很爽)。这样就解决了不可重复读这个问题。写到这里不知道你们有没有发现,不可重复读对应的是修改,即UPDATE操作。但是还是有可能出现幻读的问题,因为幻读对应的是INSERT操作,而不是UPDATE操作。那么如何解决幻读的问题呢?相信大家已经猜到了,就是使用事务隔离级别中的最高级别Serializable。
2.4.Serializable:序列化,是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读,不可重复读和幻读。说到这里,大家也许会想到既然Serializable可以解决所有问题,那为什么不把所有的隔离级别都设置为Serializable呢?其实Serializable是使用的比较少的隔离级别。为什么呢,正所谓物极必反。大家想想,既然Serializable解决了这么多问题。那么其必定是会消耗了大量的数据库性能的,所以一般是不会使用Serializable的。其实大多数数据库的默认事务隔离级别是Read committed的,比如Sql Server、Oracle、mysql的默认隔离级别就是Read committed。
那么怎么用spring配置文件的方式配置注解呢?很简单看下面代码立刻就会明白
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:context="http://www.springframework.org/schema/context" 5 xmlns:tx="http://www.springframework.org/schema/tx" 6 xmlns:aop="http://www.springframework.org/schema/aop" 7 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd 8 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd 9 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd 10 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd"> 11 12 <!-- 扫描路径 --> 13 <context:component-scan base-package="com"></context:component-scan> 14 <!-- 导入资源文件--> 15 <context:property-placeholder location="classpath:db.properties"/> 16 <!-- 配置c3p0数据源 --> 17 <bean id="dataSource" 18 class="com.mchange.v2.c3p0.ComboPooledDataSource"> 19 <property name="user" value="${jdbc.user}"></property> 20 <property name="password" value="${jdbc.password}"></property> 21 <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> 22 <property name="driverClass" value="${jdbc.driverClass}"></property> 23 <property name="initialPoolSize" value="${jdbc.initPoolSize}"></property> 24 <property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property> 25 </bean> 26 <!-- 配置JdbcTemplate --> 27 <bean id="jdbcTemplate" 28 class="org.springframework.jdbc.core.JdbcTemplate"> 29 <property name="dataSource" ref="dataSource"></property> 30 </bean> 31 <bean id = "bookShopDao" class="com.SpringTransaction.xml.BookShopDaoImpl"> 32 <property name="jdbcTemplate" ref="jdbcTemplate"></property> 33 </bean> 34 <bean id = "bookShopService" class="com.SpringTransaction.xml.service.impl.BookShopServiceImpl"> 35 <property name="bookShopDao" ref = "bookShopDao"></property> 36 </bean> 37 <bean id = "cashier" class="com.SpringTransaction.xml.service.impl.CashierImpl"> 38 <property name="bookShopService" ref="bookShopService"></property> 39 40 <!-- 1.配置事务管理器 --> 41 </bean> 42 <bean id = "transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 43 <property name="dataSource" ref="dataSource"></property> 44 </bean> 45 <!-- 2.配置事务属性 --> 46 <tx:advice id = "txAdvice" transaction-manager="transactionManager"> 47 <tx:attributes> 48 <!-- 根据方法名指定事务的属性 --> 49 <tx:method name="*"/> 50 </tx:attributes> 51 </tx:advice> 52 <!-- 3.配置事务切入点,以及把切入点和事务属性关联起来 --> 53 <aop:config> 54 <aop:pointcut expression="execution(* com.SpringTransaction.xml.service.*.*(..))" 55 id="txPointCut"/> 56 <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/> 57 </aop:config> 58 </beans>
以上是关于Spring的事务管理的主要内容,如果未能解决你的问题,请参考以下文章
初识Spring源码 -- doResolveDependency | findAutowireCandidates | @Order@Priority调用排序 | @Autowired注入(代码片段
初识Spring源码 -- doResolveDependency | findAutowireCandidates | @Order@Priority调用排序 | @Autowired注入(代码片段
spring练习,在Eclipse搭建的Spring开发环境中,使用set注入方式,实现对象的依赖关系,通过ClassPathXmlApplicationContext实体类获取Bean对象(代码片段
Spring Rest 文档。片段生成时 UTF-8 中间字节无效 [重复]
What's the difference between @Component, @Repository & @Service annotations in Spring?(代码片段