本文共 14392 字,大约阅读时间需要 47 分钟。
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;
-
-
-
-
- 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;
-
-
-
-
- INSERT INTO product VALUES ('1', 'java', '40', '10');
- INSERT INTO product VALUES ('2', 'spring', '50', '10');
事务简介
什么是事务?
事务就是一系列的动作,一个独立的最小单元。这些动作要么都成功,要么都失败。这也是它的原子性特点。
事务的关键属性
原子性:一系列的动作,要么都成功,要么都失败。
一致性:数据和事务状态要保持一致。
隔离性:为了防止数据被破坏,每个事务之间都存在隔离性。
持久性:一旦事务完成, 无论发生什么系统错误, 它的结果都不应该受到影响。
什么是事务管理?
事务管理是企业级应用程序开发中必不可少的技术, 用来确保数据的完整性和一致性.
举个例子:
A给B转账,A出账500,B因为某种原因没有成功进账。若A出账的500不回滚到A账户余额中,就会出现数据的不完整性和不一致性的问题。
文章模拟的场景是用户购买商品。商场的逻辑是:用户下单后,先发货后设置用户余额。即先减库存后减余额。如果余额充足,库存充足的情况,是没有什么问题的。若余额不足的情况购买商品会出现,库存减少了,余额因为不足而抛出异常,但最后余额并没有扣,库存却减少了。现在我们用代码看看如何用事务管理解决该问题。
基于注解的事务管理
核心文件 applicationContext.xml。用到注解,就需要配置自动扫描包context:component-scan,还需要配置JdbcTempalte。最后要配置事务管理器和启动事务注解 tx:annotation-driven。
JDBC配置的事务管理器是DataSourceTransactionManager,
Hibernate配置的事务管理器是HibernateTransactionMannger 。两个的用法都是一样,只是配置事务管理时class指定的路径不同罢了。这是因为 Spring 在不同的事务管理 API 之上定义了一个抽象层。我们无需了解底层的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"/>
-
-
- <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>
-
-
- <bean id="jdbcTemplate"
- class="org.springframework.jdbc.core.JdbcTemplate">
- <property name="dataSource" ref="dataSource"></property>
- </bean>
-
-
- <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>
-
-
- <tx:annotation-driven transaction-manager="transactionManager"/>
-
- </beans>
接下来是事务的业务代码,所有类都放在了一个目录下,没别的原因,就是因为懒。
![](https://img-blog.csdn.net/20170927104230756)
核心是消费事务类 PurchaseService。事务管理注解的用法。
其次是批量消费事务类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
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- public void purchase(String account, String sku) {
-
- float price = shopDao.getBookPriceBySku(sku);
-
-
- shopDao.updateBookStock(sku);
-
-
- shopDao.updateUserBalance(account, price);
-
-
-
-
-
- }
-
- }
批量消费事务类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");
- }
-
-
-
-
-
-
-
-
- @Test
- public void basicTransaction() {
- System.out.println("^^^^^^^^^^^^^^^^^@Transactional 最基本的使用方法");
- purchaseService.purchase("itdragon", "spring");
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- @Test
- public void propagationTransaction() {
- System.out.println("^^^^^^^^^^^^^^^^^@Transactional(propagation) 事务的传播性");
- batchPurchaseService.batchPurchase("itdragon", Arrays.asList("java", "spring"));
- }
-
-
-
-
-
-
- @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 = ?";
-
-
-
-
- return jdbcTemplate.queryForObject(sql, Float.class, sku);
- }
-
- @Override
- public void updateBookStock(String sku) {
-
- 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("库存不足!再看看其他产品吧!");
- }
-
- jdbcTemplate.update("UPDATE product SET stock = stock -1 WHERE sku = ?", sku);
- }
-
- @Override
- public void updateUserBalance(String account, float price) {
-
- 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("您的余额不足!不支持购买!");
- }
-
- jdbcTemplate.update("UPDATE user SET balance = balance - ? WHERE account = ?", price, account);
-
- 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) ![](https://img-blog.csdn.net/20170927094402928)
当用户余额10元不够买售价40元的书,书的库存充足的情况。测试noRollbackForTransaction()方法打印的结果:用户余额没有减少,但商品库存减少了,说明事务没有回滚。
![](https://img-blog.csdn.net/20170926184345738)
![](https://img-blog.csdn.net/20170926184326602)
细细品味后,其实也很简单。事务就是为了保证数据的一致性。出了问题就把之前修改过的数据回滚。
基于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"/>
-
-
- <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>
-
-
- <bean id="jdbcTemplate"
- class="org.springframework.jdbc.core.JdbcTemplate">
- <property name="dataSource" ref="dataSource"></property>
- </bean>
-
-
- <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 的教程也就结束了。感谢您的观看!!!