Spring4 事务管理

本章是Spring4 教程中的最后一章,也是非常重要的一章。如果说学习IOC是知识的入门,那学习事务管理就是知识的提升。本章篇幅可能有一丢丢长,也有一丢丢难,需要读者细细品味。主要从三个方面开始:事务简介,基于注解的事务管理 和基于xml的事务管理。


准备环境

mysql文件,两张表:一个用户表,字段有帐号和余额。一个商品表,字段有sku,售价和库存。

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (  `id` bigint(20) NOT NULL,  `account` varchar(255) NOT NULL,  `balance` float DEFAULT NULL COMMENT '用户余额',  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;  -- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO user VALUES ('1', 'itdragon', '100');  
DROP TABLE IF EXISTS `product`;
CREATE TABLE `product` (  `id` bigint(20) NOT NULL,  `sku` varchar(255) NOT NULL COMMENT '商品的唯一标识',  `price` float NOT NULL COMMENT '商品价格',  `stock` int(11) NOT NULL COMMENT '商品库存',  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;  -- ----------------------------
-- Records of product
-- ----------------------------
INSERT INTO product VALUES ('1', 'java', '40', '10');
INSERT INTO product VALUES ('2', 'spring', '50', '10');  

事务简介

工作中应该经常听到:"这是一个事务,你要保证它数据的一致性,在这里加个注解吧!"。于是我们就稀里糊涂地用,好像也没出什么问题。
因为加上注解,说明该方法支持事务的处理。事务就是一系列的动作,这一系列的动作要么都成功,要么都失败。所以你才会觉得没出什么问题。管理事务是应用程序开发必不可少的技术,用来确保数据的完整性和一致性,特别是和钱有关系的事务。
事务有四个关键属性:
** 原子性 :一系列的动作,要么都成功,要么都失败。
一致性 :数据和事务状态要保持一致。
隔离性 :为了防止数据被破坏,每个事务之间都存在隔离性。
持久性 **:一旦事务完成, 无论发生什么系统错误, 它的结果都不应该受到影响。

我们用例子更好地说明事务:
A给B转账,A出账500元,B因为某种原因没有成功进账。若A出账的500元不回滚到A账户余额中,就会出现数据的不完整性和不一致性的问题。
本章模拟用户购买商品的场景。商场的下单逻辑是:先发货后设置用户余额。如果用户余额充足,商品库存充足的情况,是没有什么问题的。但若余额不足却购买商品,库存减少了,扣除用户余额时会因为余额不足而抛出异常,到最后用户余额并没有减少,商品库存却减少了,显然是不合理的。现在我们用Spring的事务管理来解决这种问题。


基于注解的事务管理

核心文件 applicationContext.xml。既然用到注解,就需要配置自动扫描包context:component-scan,还需要配置JdbcTempalte。最后要配置事务管理器和启动事务注解 tx:annotation-driven
JDBC配置的事务管理器的class指定路径是DataSourceTransactionManager,
Hibernate配置的事务管理器的class指定路径是HibernateTransactionMannger 。两个的用法都是一样,只是配置事务管理时class指定的路径不同罢了。这是因为 Spring 在不同的事务管理上定义了一个抽象层。我们无需了解底层的API,就可以使用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"  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd  http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">  <context:component-scan base-package="com.itdragon.spring"></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>  <!-- 配置 Spirng 的 JdbcTemplate -->  <bean id="jdbcTemplate"   class="org.springframework.jdbc.core.JdbcTemplate">  <property name="dataSource" ref="dataSource"></property>  </bean>  <!-- 配置 NamedParameterJdbcTemplate, 该对象可以使用具名参数, 其没有无参数的构造器, 所以必须为其构造器指定参数 -->  <bean id="namedParameterJdbcTemplate"  class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">  <constructor-arg ref="dataSource"></constructor-arg>      </bean>  <!-- 配置事务管理器 -->  <bean id="transactionManager"   class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  <property name="dataSource" ref="dataSource"></property>  </bean>  <!-- 启用事务注解  如果配置的事务管理器的id就是transactionManager , 这里是可以省略transaction-manager -->  <tx:annotation-driven transaction-manager="transactionManager"/>  </beans>  

接下来是事务的业务代码,所有类都放在了一个目录下,没别的原因,就是因为懒。

核心是消费事务类 PurchaseService。介绍事务注解@Transactional的语法。
其次是批量消费事务类BatchPurchaseService。用于配合PurchaseService测试事务的传播性。
然后是事务测试类TransactionTest。主要负责测试和详细解释事务语法。
最后是自定义异常类。是为了测试事务的回滚属性。
PurchaseService(重点,注解语法),测试时,将注解逐一放开。

import java.util.List;
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
public class PurchaseService {  @Autowired  private ShopDao shopDao;  /** * 模拟用户购买商品,测事务回滚 * 最基本用法,直接在方法或者类上使用注解@Transactional。值得注意的是:只能在公共方法上使用 * 对应的测试方法是 basicTransaction() */  @Transactional  /** * 事务的传播 propagation=Propagation.REQUIRED * 常用的有两种 REQUIRED,REQUIRES_NEW * 对应的测试方法是  propagationTransaction() */
//  @Transactional(propagation=Propagation.REQUIRED)  /** * 事务的隔离性 * 将事务隔离起来,减少在高并发的场景下发生 脏读,幻读和不可重复读的问题 * 默认值是READ_COMMITTED 只能避免脏读的情况。 * 不好演示,没有对应的测试方法。 */
//  @Transactional(isolation=Isolation.READ_COMMITTED)  /** * 回滚事务属性 * 默认情况下声明式事务对所有的运行时异常进行回滚,也可以指定某些异常回滚和某些异常不回滚。(意义不大) * noRollbackFor 指定异常不回滚 * rollbackFor 指定异常回滚 */
//  @Transactional(noRollbackFor={UserException.class, ProductException.class})  /** * 超时和只读属性 * 超时:在指定时间内没有完成事务则回滚。可以减少资源占用。参数单位是秒 * 如果超时,则提示错误信息: * org.springframework.transaction.TransactionTimedOutException: Transaction timed out * 只读属性:指定事务是否为只读. 若事务只读数据则有利于数据库引擎优化事务。  * 因为该事务有修改数据的操作,若设置只读true,则提示错误信息 * nested exception is java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed * 对应的测试方法是 basicTransaction() */
//  @Transactional(timeout=5, readOnly=false)  public void purchase(String account, String sku) {  //1. 获取书的单价  float price = shopDao.getBookPriceBySku(sku);  //2. 更新数的库存  shopDao.updateBookStock(sku);  //3. 更新用户余额  shopDao.updateUserBalance(account, price);  // 测试超时用的  /*try { Thread.sleep(6000); } catch (InterruptedException e) { }*/  }  }  

批量消费事务类BatchPurchaseService

import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;  @Service
public class BatchPurchaseService {  @Autowired  private PurchaseService purchaseService;  // 批量采购书籍,事务里面有事务  @Transactional  public void batchPurchase(String username, List<String> skus) {  for (String sku : skus) {  purchaseService.purchase(username, sku);  }  }  }  

事务测试类TransactionTest(重点,知识点说明

import java.util.Arrays;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;  public class TransactionTest {  private ApplicationContext ctx = null;  private PurchaseService purchaseService = null;  private BatchPurchaseService batchPurchaseService = null;  {  ctx = new ClassPathXmlApplicationContext("applicationContext.xml");  purchaseService = (PurchaseService) ctx.getBean("purchaseService");  batchPurchaseService = (BatchPurchaseService) ctx.getBean("batchPurchaseService");  }  /** * 用户买一本书 * 基本用法-事务回滚 * 把@Transactional 注释。假设当前用户余额只有10元。单元测试后,用户余额没有变,spring的库存却减少了。赚了!!! * 把@Transactional 注释打开。假设当前用户余额只有10元。单元测试后,用户余额没有变,spring的库存也没有减少。这就是回滚。 * 回滚:按照业务逻辑,先更新库存,再更新余额。现在是库存更新成功了,但在余额逻辑抛出异常。最后数据库的值都没有变。也就是库存回滚了。 */  @Test  public void basicTransaction() {  System.out.println("^^^^^^^^^^^^^^^^^@Transactional 最基本的使用方法");  purchaseService.purchase("itdragon", "spring");  }  /** * 用户买多本书 * 事务的传播性 -大事务中,有小事务,小事务的表现形式 * 用@Transactional, 当前用户余额50,是可以买一本书的。运行结束后,数据库中用户余额并没有减少,两本书的库存也都没有减少。 * 用@Transactional(propagation=Propagation.REQUIRED), 运行结果是一样的。 * 把REQUIRED 换成 REQUIRES_NEW 再运行 结果还是一样。。。。。 * 为什么呢???? 因为我弄错了!!!!! * 既然是事务的传播性,那当然是一个事务传播给另一个事务。 * 需要新增一个事务类批量购买 batchPurchase事务, 包含了purchase事务。 * 把 REQUIRED 换成 REQUIRES_NEW 运行的结果是:用户余额减少了,第一本书的库存也减少了。 * REQUIRED:如果有事务在运行,当前的方法就在这个事务内运行。否则,就启动一个新的事务,并在自己的事务内运行。大事务回滚了,小事务跟着一起回滚。 * REQUIRES_NEW:当前的方法必须启动新事务,并在自己的事务内运行。如果有事务在运行,应该将它挂起。大事务虽然回滚了,但是小事务已经结束了。 */  @Test  public void propagationTransaction() {  System.out.println("^^^^^^^^^^^^^^^^^@Transactional(propagation) 事务的传播性");  batchPurchaseService.batchPurchase("itdragon", Arrays.asList("java", "spring"));  }  /** * 测试异常不回滚,故意超买(不常用) * 当前用户余额10元,买了一本价值40元的java书。运行结束后,余额没有少,java书的库存减少了(赚了!)。因为设置指定异常不回滚! * 指定异常回滚就不测了。 */  @Test  public void noRollbackForTransaction() {  System.out.println("^^^^^^^^^^^^^^^^^@Transactional(noRollbackFor) 设置回滚事务属性");  purchaseService.purchase("itdragon", "java");  }
}  

业务处理接口以及接口实现类

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;  @Repository("shopDao")
public class ShopDaoImpl implements ShopDao {  @Autowired  private JdbcTemplate jdbcTemplate;  @Override  public float getBookPriceBySku(String sku) {  String sql = "SELECT price FROM product WHERE sku = ?";  /** * 第二个参数要用封装数据类型,如果用float.class,会提示 Type mismatch affecting row number 0 and column type 'FLOAT':  * Value [40.0] is of type [java.lang.Float] and cannot be converted to required type [float] 错误 */  return jdbcTemplate.queryForObject(sql, Float.class, sku);  }  @Override  public void updateBookStock(String sku) {  // step1 防超卖,购买前先检查库存。若不够, 则抛出异常  String sql = "SELECT stock FROM product WHERE sku = ?";  int stock = jdbcTemplate.queryForObject(sql, Integer.class, sku);  System.out.println("^^^^^^^^^^^^^^^^^商品( " + sku + " )可用库存 : " + stock);  if(stock == 0){  throw new ProductException("库存不足!再看看其他产品吧!");  }  // step2 更新库存  jdbcTemplate.update("UPDATE product SET stock = stock -1 WHERE sku = ?", sku);  }  @Override  public void updateUserBalance(String account, float price) {  // step1 下单前验证余额是否足够, 若不足则抛出异常  String sql = "SELECT balance FROM user WHERE account = ?";  float balance = jdbcTemplate.queryForObject(sql, Float.class, account);  System.out.println("^^^^^^^^^^^^^^^^^您当前余额 : " + balance + ", 当前商品价格 : " + price);  if(balance < price){  throw new UserException("您的余额不足!不支持购买!");  }  // step2 更新用户余额  jdbcTemplate.update("UPDATE user SET balance = balance - ? WHERE account = ?", price, account);  // step3 查看用于余额  System.out.println("^^^^^^^^^^^^^^^^^您当前余额 : " + jdbcTemplate.queryForObject(sql, Float.class, account));  }  } 

最后两个自定义的异常类

public class UserException extends RuntimeException{  private static final long serialVersionUID = 1L;  public UserException() {  super();  }  public UserException(String message) {  super(message);  }  }  
public class ProductException extends RuntimeException{  private static final long serialVersionUID = 1L;  public ProductException() {  super();  }  public ProductException(String message) {  super(message);  }  }  

当用户余额10元不够买售价为50的书,书的库存充足的情况。测试basicTransaction()方法打印的结果:用户余额不减少,库存也不减少

当用户余额50元准备购买两本总价为90的书,但余额只够买一本书,书的库存充足的情况,测试propagationTransaction()方法打印的结果:若用 REQUIRES_NEW则两本中可以买一本;若用REQUIRED则一本都买不了。(事务的传播性有7种,这里主要介绍常用的REQUIRED和REQUIRES_NEW)

当用户余额10元不够买售价40元的书,书的库存充足的情况。测试noRollbackForTransaction()方法打印的结果:用户余额没有减少,但商品库存减少了,说明事务没有回滚。

细细品味后,其实也很简单。事务就是为了保证数据的一致性。出了问题就把之前修改过的数据回滚。


基于xml的事务管理

如果你理解了基于注解的事务管理,那基于xml的事务管理就简单多了。由于篇幅已经太长了,这里我长话短说。
首先把上面的java类中的所有IOC注解,@Transactional注解和@Autowired去掉。被@Autowired修饰的属性,还需要另外生成setter方法。
然后配置applicationContext.xml文件。将启动事务注解的代码删掉。将之前用自动扫描包的IOC注解和@Autowired注解的代码都配置bean(IOC知识),然后 配置事务属性,最后 配置事务切入点(AOP知识),这是系列博客,不懂的可以看前面几章。

<?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/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  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">  <!-- 导入资源文件 -->  <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>  <!-- 配置 Spirng 的 JdbcTemplate -->  <bean id="jdbcTemplate"   class="org.springframework.jdbc.core.JdbcTemplate">  <property name="dataSource" ref="dataSource"></property>  </bean>  <!-- 配置 NamedParameterJdbcTemplate, 该对象可以使用具名参数, 其没有无参数的构造器, 所以必须为其构造器指定参数 -->  <bean id="namedParameterJdbcTemplate"  class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">  <constructor-arg ref="dataSource"></constructor-arg>      </bean>  <bean id="shopDao" class="com.itdragon.spring.my.transactionxml.ShopDaoImpl">  <property name="jdbcTemplate" ref="jdbcTemplate"></property>  </bean>  <bean id="purchaseService" class="com.itdragon.spring.my.transactionxml.PurchaseService">  <property name="shopDao" ref="shopDao"></property>  </bean>  <bean id="batchPurchaseService" class="com.itdragon.spring.my.transactionxml.BatchPurchaseService">  <property name="purchaseService" ref="purchaseService"></property>  </bean>  <!-- 配置事务管理器 -->  <bean id="transactionManager"   class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  <property name="dataSource" ref="dataSource"></property>  </bean>  <!-- 配置事务属性 -->  <tx:advice id="txAdvice" transaction-manager="transactionManager">  <tx:attributes>  <!-- 根据方法名指定事务的属性 -->  <tx:method name="purchase"   propagation="REQUIRES_NEW"   timeout="3"   read-only="false"/>  <tx:method name="batchPurchase"/>  </tx:attributes>  </tx:advice>  <!-- 配置事务切入点 -->  <aop:config>  <aop:pointcut expression="execution(* com.itdragon.spring.my.transactionxml.PurchaseService.purchase(..))"   id="pointCut"/>  <aop:advisor advice-ref="txAdvice" pointcut-ref="pointCut"/>  </aop:config>  <aop:config>  <aop:pointcut expression="execution(* com.itdragon.spring.my.transactionxml.BatchPurchaseService.batchPurchase(..))"   id="batchPointCut"/>  <aop:advisor advice-ref="txAdvice" pointcut-ref="batchPointCut"/>  </aop:config>
</beans> 

代码亲测可用。有什么错误地方可以指出。

到这里Spring4 的教程也就结束了。感谢您的观看!!!

转载于:https://www.cnblogs.com/itdragon/p/7717974.html

Spring4 事务管理相关推荐

  1. Spring4.x❹ Spring的事务管理 DataSourceTransactionManager

    1 Spring事务管理? 2 Spring 事务管理案例 2.1 实体类 2.2 dao 2.3 service 2.4 Spring配置文件 2.5 test 3 service中的事务管理 3. ...

  2. Spring JDBC-Spring对事务管理的支持

    概述 事务管理关键抽象 Spring事务管理的实现类 Spring JDBC 和MybBatis的事务管理器的配置 JPA的事务管理器的配置 Hibernate的事务管理器的配置 JTA 的事务管理器 ...

  3. Spring之事务管理配置

    1. 基于注解的事务配置 1. 在需要添加事务的方法上加上@Transactional注解 2. Spring的配置文件中配置事务管理器 1 <!-- 添加事务管理器组件DataSourceTr ...

  4. Spring事务管理只对出现运行期异常进行回滚

    使用spring难免要用到spring的事务管理,要用事务管理又会很自然的选择声明式的事务管理,在spring的文档中说道,spring声明式事务管理默认对非检查型异常和运行时异常进行事务回滚,而对检 ...

  5. mybatis源码分析之事务管理器

    2019独角兽企业重金招聘Python工程师标准>>> 上一篇:mybatis源码分析之Configuration 主要分析了构建SqlSessionFactory的过程中配置文件的 ...

  6. Spring事务管理 与 SpringAOP

    1,Spring事务的核心接口 Spring事务管理的实现有许多细节,如果对整个接口框架有个大体了解会非常有利于我们理解事务,下面通过讲解Spring的事务接口来了解Spring实现事务的具体策略.  ...

  7. Spring事务管理3----声明式事务管理(1)

     声明式事务管理(1)基于    基于 tx/aop  这种事务管理相比编程式事务管理来说对业务层基本没有改动,通过  TransactionProxyFactoryBean 创建业务层的代理,通过A ...

  8. 什么是事务的传播_这么漂亮的Spring事务管理详解,你不来看看?

    事务概念回顾 什么是事务? 事务是逻辑上的一组操作,要么都执行,要么都不执行. 事物的特性(ACID): 原子性: 事务是最小的执行单位,不允许分割.事务的原子性确保动作要么全部完成,要么完全不起作用 ...

  9. SpringMVC+MyBatis 事务管理一

    前言 spring事务管理包含两种情况,编程式事务.声明式事务.而声明式事务又包括基于注解@Transactional和tx+aop的方式.那么本文先分析编程式注解事务和基于注解的声明式事务. 编程式 ...

最新文章

  1. 第54期《SCI论文插图排版与绘制》课程欢迎你!
  2. VS2005+SQL2005 ASP.NET2.0数据库连接
  3. matlab图像融合评价,MATLAB 图像融合评估算法
  4. 如何在windows7上安装启明星系统。
  5. Mysql忘记用户密码的解决办法
  6. BZOJ 4802: 欧拉函数(大数因数分解算法 Pollard_rho 和素数测试算法 Miller_Rabin)
  7. android通用的UUID唯一标示符
  8. window linux IPC ftok BY_HANDLE_FILE_INFORMATION
  9. P1311,jzoj3025-选择客栈【统计】
  10. Linux进程调度器-基础
  11. android 4.4 屏幕方向,Android4.4屏幕旋转功能
  12. python 【第一篇】初识python
  13. java雪花算法实现
  14. Kero---knockout
  15. Win10+Ubuntu双系统修复Ubuntu系统引导
  16. python筛选excel符合条件的数据——python处理excel数据(四)
  17. 新浪短连接(t.cn)在线生成工具
  18. js 获取输入的首字母拼音
  19. 别样肉客首次针对中国市场独家研发创新植物性猪肉糜产品
  20. ps cs6 mac破解方法

热门文章

  1. 计算机视觉论文-2021-07-09
  2. CVPR2020 | 华为GhostNet超越谷歌MobileNetV3
  3. 中如何调取api_【技巧】不去腾讯公司也能调取微信交易流水清单
  4. centos7开机启动自定义脚本_在Centos 7系统中开启启动自定义脚本的方法
  5. JAVA里面pressed的反义词_pressed是什么意思_pressed怎么读_pressed翻译_用法_发音_词组_同反义词_紧缺的-新东方在线英语词典...
  6. 00038oracle,ORACLE错误一览表
  7. 分别求两个整数的最大公约数和最小公倍数。_看不懂辗转相除法求最小公约数?以身相许那种哦!...
  8. Encoder-Decoder模型和Attention模型
  9. MySQL两种存储引擎: MyISAM和InnoDB
  10. Vue.js 基础学习