那么什么是事务呢? 相信大家都知道事物是怎么一回事吧. 为了防止有些人忘记了, 现在我在简要的说下什么是事务.
事务其实就是一系列的动作, 它们被当做一个单独的工作单元. 这些动作要么全部完成, 要么全部不起作用. 比如说我们在淘宝买东西扣钱的时候, 这时候我们余额要减少同时库存也要减少, 这两个操作要么都完成, 要么都不完成. 如果一个完成一个不完成, 那这样要么用户少了钱要么库存少了件, 这就是事务.
事务的四个关键属性 (ACID)
原子性 (atomicity): 事务是一个原子操作, 由一系列动作组成. 事务的原子性确保动作要么全部完成要么完全不起作用.
一致性 (consistency): 一旦所有事务动作完成, 事务就被提交. 数据和资源就处于一种满足业务规则的一致性状态中.
隔离性 (isolation): 可能有许多事务会同时处理相同的数据, 因此每个事物都应该与其他事务隔离开来, 防止数据损坏.
持久性 (durability): 一旦事务完成, 无论发生什么系统错误, 它的结果都不应该受到影响. 通常情况下, 事务的结果被写到持久化存储器中.
那么在 spring 中是如何管理事物的呢? 其实非常简单, 用注解的方法只需要写几句话就 OK 了. 那么我们来看一看把.
数据库文件和一些 Dao 什么的我就不贴出来了, 参照我下面的例子应该非常容易理解. 首先我们现在 spring 配置文件中配置事务管理器和启用事物的注解 (需要导入 tx 命名空间)
- <!-- 配置事务管理器 -->
- <bean id = "transactionManager"
- class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
- <property name="dataSource" ref = "dataSource"></property>
- </bean>
- <!-- 启用事物注解 -->
- <tx:annotation-driven transaction-manager="transactionManager"/>
随后在 Service 层中启用事务注解
- package com.SpringTransaction;
- import org.junit.rules.Timeout;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Service;
- import org.springframework.transaction.annotation.Isolation;
- import org.springframework.transaction.annotation.Propagation;
- import org.springframework.transaction.annotation.Transactional;
- @Service("BookShopService")
- public class BookShopServiceImpl implements BookShopService {
- @Autowired
- private BookShopDao bookShopDao;
- /*
- * 添加事务注解 (@Transactional)
- *1. 使用 propagation 指定事务的传播行为, 即当前的事务方法被另外一个事务方法调用时
- * 如何使用事务, 默认取值为 REQUIRED, 即使用调用方法的事务
- *2. 使用 isolation 来确定事务的隔离级别, 最常用的取值为 READ_COMMITTED
- *3.noRollbackFor, 通常情况下 spring 的声明式事物会对所有运行时的异常进行回滚, 也可以通过 noRollbackFor 来
- * 设置不需要回滚的异常. 通常情况下使用默认值就行了.
- * @Transactional(propagation = Propagation.REQUIRED,
- * isolation=Isolation.READ_COMMITTED,
- * RollbackFor={UserAccountException.class})
- *4.readOnly: 使用 readOnly 可以指定事务是否只读, 若为 true 表示只读,
- * 这样可以帮助数据库引擎优化事务. 若真的事一个只读取数据库值的方法, 应设置 readOnly=true
- *5.timeout 指定强制回滚之前事务可以占用的时间. 防止一个访问长时间占用
- * */
- @Transactional(propagation = Propagation.REQUIRED,
- isolation=Isolation.READ_COMMITTED,
- readOnly = false,
- timeout=3)
- public void purchase(String username, int bookid) {
- //1. 获取书的单价
- int price = bookShopDao.findBookPriceByBookId(bookid);
- //2. 更新书的库存
- bookShopDao.updateBookStock(bookid);
- //3. 更新用户余额
- bookShopDao.updateUserAccount(username, price);
- }
- }
现在我来说说注解中 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 配置文件的方式配置注解呢? 很简单看下面代码立刻就会明白
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:context="http://www.springframework.org/schema/context"
- xmlns:tx="http://www.springframework.org/schema/tx"
- xmlns:aop="http://www.springframework.org/schema/aop"
- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
- http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
- http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
- http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
- <!-- 扫描路径 -->
- <context:component-scan base-package="com"></context:component-scan>
- <!-- 导入资源文件 -->
- <context:property-placeholder location="classpath:db.properties"/>
- <!-- 配置 c3p0 数据源 -->
- <bean id="dataSource"
- class="com.mchange.v2.c3p0.ComboPooledDataSource">
- <property name="user" value="${jdbc.user}"></property>
- <property name="password" value="${jdbc.password}"></property>
- <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
- <property name="driverClass" value="${jdbc.driverClass}"></property>
- <property name="initialPoolSize" value="${jdbc.initPoolSize}"></property>
- <property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
- </bean>
- <!-- 配置 JdbcTemplate -->
- <bean id="jdbcTemplate"
- class="org.springframework.jdbc.core.JdbcTemplate">
- <property name="dataSource" ref="dataSource"></property>
- </bean>
- <bean id = "bookShopDao" class="com.SpringTransaction.xml.BookShopDaoImpl">
- <property name="jdbcTemplate" ref="jdbcTemplate"></property>
- </bean>
- <bean id = "bookShopService" class="com.SpringTransaction.xml.service.impl.BookShopServiceImpl">
- <property name="bookShopDao" ref = "bookShopDao"></property>
- </bean>
- <bean id = "cashier" class="com.SpringTransaction.xml.service.impl.CashierImpl">
- <property name="bookShopService" ref="bookShopService"></property>
- <!-- 1. 配置事务管理器 -->
- </bean>
- <bean id = "transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
- <property name="dataSource" ref="dataSource"></property>
- </bean>
- <!-- 2. 配置事务属性 -->
- <tx:advice id = "txAdvice" transaction-manager="transactionManager">
- <tx:attributes>
- <!-- 根据方法名指定事务的属性 -->
- <tx:method name="*"/>
- </tx:attributes>
- </tx:advice>
- <!-- 3. 配置事务切入点, 以及把切入点和事务属性关联起来 -->
- <aop:config>
- <aop:pointcut expression="execution(* com.SpringTransaction.xml.service.*.*(..))"
- id="txPointCut"/>
- <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
- </aop:config>
- </beans>
来源: https://www.cnblogs.com/lqgcn/p/10772999.html