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?(代码片段

使用 Git 来管理 Xcode 中的代码片段