首页IT科技声明式事务处理(day16-声明式事务-02)

声明式事务处理(day16-声明式事务-02)

时间2025-05-05 00:02:30分类IT科技浏览3525
导读:声明式事务-02 3.事务的传播机制...

声明式事务-02

3.事务的传播机制

事务的传播机制说明:

当有多个事务处理并存时           ,如何控制?

比如用户去购买两次商品(使用不同的方法)                 ,每个方法都是一个事务    ,那么如何控制呢?

也就是说        ,某个方法本身是一个事务                  ,然后该方法中又调用了其他一些方法       ,这些方法也是被@Transactional 修饰的     ,同样是事务           。

问题在于:里层方法的事务是被外层方法事务管理?还是它本身作为一个独立的事务呢?这就涉及到事务的传播机制问题                 。

3.1事务传播机制种类

事务传播的属性 / 种类: 传播属性 说明 REQUIRED (默认)如果有事务在运行                  ,当前的方法就在这个事务内运行          ,否则  ,就启动一个新的事务                 ,并且在自己的事务内运行 REQUIRES_NEW 当前的方法必须启动新事务             ,并在它自己的事务内运行,如果有事务正在运行              ,应该将它挂起 SUPPORTS 如果有事务在运行                ,当前的方法就在这个事务内运行  ,否则它可以不运行在事务中 NOT_SUPPORTED 当前的方法不应该运行在事务中           ,如果有运行的事务                 ,将它挂起 MANDATORY 当前的方法必须运行在事务内部    ,如果没有正在运行的事务        ,就抛出异常 NEVER 当前的方法不应该运行在事务中                  ,如果有运行的事务       ,就抛出异常 NESTED 如果有事务在运行     ,当前的方法就应该在这个事务的嵌套事务内运行                  ,否则          ,就启动一个新的事务  ,并在它自己的事务内运行

常用的只有前面两种:(1)REQUIRED                 ,(2)REQUIRES_NEWREQUIRES_NEW

事务传播的属性/种类机制分析

重点分析 REQUIRED 和 REQUIRES_NEW 两种事务传播属性             ,其他知道即可    。

如下,有一个multiTxTest()方法              ,该方法中又有f1()                ,f2() 方法        。所有方法都分别开启了声明式事务                  。

@Transactional public void multiTxTest() { f1(); //含事务 f2(); //含事务 }

如果f1()  ,f2() 的传播属性都是 REQUIRED           ,那么它们实际上是被Tx()的事务统一管理的       。所有方法是一个整体                 ,只要有一个方法的事务错误    ,那么两个方法都不会执行成功     。

如果f1()        ,f2() 的传播属性都是 REQUIRES_NEW                  ,那么f1()       ,f2()实际上是独立的事务     ,不会受到Tx()事务的影响                  。如果f1()错误                  ,不会影响到f2()          ,反之亦然          。

3.2应用实例

需求说明:

用户要去购买两次商品(使用不同的方法)  ,每个方法都是一个事务                 ,那么如何控制呢? 看一个具体的案例(用 required 和 requires_new 测试)

代码实现

1.GoodsDao.java

分别有6个方法:queryPriceById             ,queryPriceById2,updateBalance              ,updateBalance2                ,updateAmount  ,updateAmount2  。

package com.li.tx.dao; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import javax.annotation.Resource; /** * @author 李 * @version 1.0 */ @Repository //将GoodsDao对象 注入到 spring 容器 public class GoodsDao { @Resource private JdbcTemplate jdbcTemplate; /** * 根据商品id           ,查询对应的商品价格 * @param id * @return */ public Float queryPriceById(Integer id) { String sql = "select price from goods where goods_id = ?"; Float price = jdbcTemplate.queryForObject(sql, Float.class, id); return price; } /** * 修改用户余额 [减少用户余额] * @param user_id * @param money */ public void updateBalance(Integer user_id, Float money) { String sql = "update user_account set money=money-? where user_id=? "; jdbcTemplate.update(sql, money, user_id); } /** * 修改商品库存量 * @param goods_id * @param amount */ public void updateAmount(Integer goods_id, int amount) { String sql = "update goods_amount set goods_num=goods_num-? where goods_id=? "; jdbcTemplate.update(sql, amount, goods_id); } //和queryPriceById的操作是一样的 public Float queryPriceById2(Integer id) { String sql = "select price from goods where goods_id = ?"; Float price = jdbcTemplate.queryForObject(sql, Float.class, id); return price; } //和updateBalance的操作是一样的 public void updateBalance2(Integer user_id, Float money) { String sql = "update user_account set money=money-? where user_id=? "; jdbcTemplate.update(sql, money, user_id); } //和updateAmount的操作是一样的 public void updateAmount2(Integer goods_id, int amount) { String sql = "update goods_amount set goods_num=goods_num-? where goods_id=? "; jdbcTemplate.update(sql, amount, goods_id); } }

2.GoodsService.java                 ,分别有两个方法buyGoodsByTx    ,buyGoodsByTx02

package com.li.tx.service; import com.li.tx.dao.GoodsDao; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.beans.Transient; /** * @author 李 * @version 1.0 */ @Service //将GoodsService对象注入到容器中 public class GoodsService { @Resource private GoodsDao goodsDao; /** * 进行商品购买的方法 * @param userId * @param goodsId * @param amount */ @Transactional public void buyGoodsByTx(int userId, int goodsId, int amount) { //输出购买的相关信息 System.out.println("用户购买信息 userId=" + userId + " goodsId=" + goodsId + " 购买数量=" + amount); //1.得到商品价格 Float price = goodsDao.queryPriceById(goodsId); //2.减少用户余额 goodsDao.updateBalance(userId, price * amount); //3.减少商品库存量 goodsDao.updateAmount(goodsId, amount); System.out.println("用户购买成功..."); } /** * 进行商品购买的方法02        ,调用的是GoodsDao的2后缀的方法 * @param userId * @param goodsId * @param amount */ @Transactional public void buyGoodsByTx02(int userId, int goodsId, int amount) { //输出购买的相关信息 System.out.println("用户购买信息 userId=" + userId + " goodsId=" + goodsId + " 购买数量=" + amount); //1.得到商品价格 Float price = goodsDao.queryPriceById2(goodsId); //2.减少用户余额 goodsDao.updateBalance2(userId, price * amount); //3.减少商品库存量 goodsDao.updateAmount2(goodsId, amount); System.out.println("用户购买成功..."); } }

3.MultiplyService.java

package com.li.tx.service; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; /** * @author 李 * @version 1.0 */ @Service public class MultiplyService { @Resource private GoodsService goodsService; /** * 说明 * 1.multiBuyGoodsByTx() 方法中                  ,有两次购商品的操作 * 2.buyGoodsByTx 和 buyGoodsByTx02 都是声明式事务 * 3.并且buyGoodsByTx 和 buyGoodsByTx02使用的传播属性为默认的 REQUIRED       , * 即会当做一个整体事务来处理 */ @Transactional public void multiBuyGoodsByTx() { goodsService.buyGoodsByTx(1, 1, 1); goodsService.buyGoodsByTx02(1, 1, 1); } }

4.测试

//测试事务的传播机制 @Test public void multiBuyGoodsByTx(){ ApplicationContext ioc = new ClassPathXmlApplicationContext("tx.xml"); MultiplyService multiplyService = ioc.getBean(MultiplyService.class); multiplyService.multiBuyGoodsByTx(); }

测试结果:购买成功

测试前数据:

表结构详见上一篇

测试后数据:

5.在GoodsDao的updateAmount2()方法中添加错误字符     ,使其不能成功执行:

因为 buyGoodsByTx() 和buyGoodsByTx02() 的事务传播属性都是required                  ,且都在multiBuyGoodsByTx()方法内部          ,因此它们被视为一个整体                 。当 buyGoodsByTx02() 执行出现错误  ,两个方法将会一起回滚             。

执行4.的测试代码                 ,测试结果:出现异常。

测试后数据:

仍然是之前的数据             ,说明两个方法一起进行了事务回滚              。

6.将GoodsService 的 buyGoodsByTx() / buyGoodsByTx02() 方法的事务传播属性改为REQUIRES_NEW                。

这时两个方法的事务是独立的,buyGoodsByTx02() 失败不会造成 buyGoodsByTx() 的回滚  。

7.再执行4.测试方法              ,结果如下:仍然出现异常

但是只有 buyGoodsByTx() 方法操作改变了数据           。

测试前数据:

测试后数据:

说明只有 buyGoodsByTx02() 方法进行了回滚                 。

4.事务的隔离机制

4.1事务隔离级别说明

MySQL 隔离级别定义了事务与事务之间的隔离程度

MySQL隔离级别(4种) 脏读 不可重复读 幻读 加锁读 读未提交(Read uncommitted) v v v 不加锁 读已提交(Read committed) x v v 不加锁 可重复读(Repeatable read) x x x 不加锁 可串行化(Serializable) x x x 加锁

关于可重复读会不会发生幻读问题:

SQL92标准有                ,mysql数据库改进了  ,解决了这个级别的幻读问题    。

事务隔离级别说明

Spring声明式事务的默认隔离级别           ,就是 mysql 数据库默认的隔离级别                 ,一般为 REPREATABLE_READ

查看源码可知:Use the default isolation level of the underlying datastore. All other levels correspond to the JDBC isolation levels.

查看数据库的隔离级别 SELECT @@global.tx_isolation

4.2事务隔离级别的设置和测试

整体思路如下:

在开启了声明式事务的某方法中    ,查询两次数据        。在第一次查询后        ,先在控制台中修改该数据(在终端中默认为自动提交)                  ,方法再进行第二次的查询                  。查看两次查询的数据是否相同       。通过这样的方法来模拟两个客户端       ,测试声明式事务的隔离级别     。

1.修改GoodsService.java     ,先测试默认隔离级别                  ,增加方法 buyGoodsByTxISOLATION()

/** * 在默认下          ,声明式事务使用的隔离界别为 可重复读-Repeatable read */ @Transactional public void buyGoodsByTxISOLATION() { //查询两次商品的价格 Float price = goodsDao.queryPriceById(1); System.out.println("第一次查询的价格=" + price); Float price2 = goodsDao.queryPriceById(1); System.out.println("第二次查询的价格=" + price2); }

并在方法如下位置打上断点

2.测试方法

//测试声明式事务的隔离级别 @Test public void buyGoodsByTxISOLATIONTest() { ApplicationContext ioc = new ClassPathXmlApplicationContext("tx.xml"); GoodsService goodsService = ioc.getBean(GoodsService.class); goodsService.buyGoodsByTxISOLATION(); }

3.点击debug  ,当光标跳转到断点时                 ,可以看到第一次查询的 price=10

4.这时我们在控制台修改该数据为 15

5.然后点击Step Over             ,发现第二次查询的价格仍然为 10

这说明Spring的声明是事务的默认隔离级别为 可重复读                  。

6.将方法buyGoodsByTxISOLATION() 的事务隔离级别改为 读已提交

读已提交表示只要是提交的数据,在当前事务中都可以读取到最新数据

同时和之前一样打上断点          。

7.测试方法不变              ,点击debug                ,光标跳转到断点时  ,可以看到第一次查询时 price=15

8.此时在控制台将该数据改为 20

9.点击Step Over           ,可以看到第二次查询的数据已经变成了 20

说明当前事务的隔离级别为 读已提交  。

4.3事务的超时回滚

基本介绍 如果一个事务执行的时间超过某个时间限制                 ,就让该事务回滚                 。 可以通过设置事务超时回滚来实现 基本语法

例子:超时回滚代码实现

1.GoodsService 中增加方法 buyGoodsByTxTimeout()    ,并设置事务超时时间为2s             。为了模拟超时效果        ,在方法中休眠4s。

/** * 1.timeout = 2                  ,表示该方法如果执行时间超过了两秒       ,就进行回滚 * 2.如果没有设置 timeout     ,则默认该值为 -1                  ,表示使用默认超时时间          , * 一般为连接的数据库的默认超时时间 */ @Transactional(timeout = 2) public void buyGoodsByTxTimeout(int userId, int goodsId, int amount){ //输出购买的相关信息 System.out.println("用户购买信息 userId=" + userId + " goodsId=" + goodsId + " 购买数量=" + amount); //1.得到商品价格 Float price = goodsDao.queryPriceById2(goodsId); //2.减少用户余额 goodsDao.updateBalance2(userId, price * amount); //模拟超时 System.out.println("==========超时开始4s========="); try { Thread.sleep(4000);//休眠4s } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("==========超时结束4s========="); //3.减少商品库存量 goodsDao.updateAmount2(goodsId, amount); System.out.println("用户购买成功..."); }

2.测试方法

//测试超时 timeout 属性 @Test public void buyGoodsByTxTimeoutTest() { ApplicationContext ioc = new ClassPathXmlApplicationContext("tx.xml"); GoodsService goodsService = ioc.getBean(GoodsService.class); goodsService.buyGoodsByTxTimeout(1, 1, 1); }

测试结果:出现异常  ,显示事务超时              。

测试前数据:

测试后数据:

数据没有进行改变                 ,说明事务超时             ,并进行了回滚                。

5.练习

要求:模拟一个用户,进行银行转账              ,购买淘宝商品的业务  。数据表                ,dao层  ,service层自己设置           ,要求保证数据一致性           。

seller [卖家表] buyer [买家表] goods [商品表[有库存量属性]] taoBao [taoBao表                 ,提取入账成交额的 10%] 要求简单实现    ,使用声明式事务完成 要求创建新的spring容器文件 shopping_ioc.xml        ,完成测试

实现

1.创建表格                  ,并插入初始数据

-- buyer表 CREATE TABLE `buyer`( buyer_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, buyer_name VARCHAR(32) NOT NULL DEFAULT , buyer_money DOUBLE NOT NULL DEFAULT 0.0 )CHARSET=utf8; INSERT INTO `buyer` VALUES(NULL,张三, 1000); INSERT INTO `buyer` VALUES(NULL,李四, 2000); -- seller表 CREATE TABLE `seller`( seller_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, seller_name VARCHAR(32) NOT NULL DEFAULT , seller_money DOUBLE NOT NULL DEFAULT 0.0 )CHARSET=utf8 ; INSERT INTO `seller` VALUES(NULL,卖家1, 0); INSERT INTO `seller` VALUES(NULL,卖家2, 0); -- goods表 CREATE TABLE `goods`( goods_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, goods_name VARCHAR(32) NOT NULL DEFAULT , price DOUBLE NOT NULL DEFAULT 0.0, seller_id INT UNSIGNED, goods_num INT UNSIGNED DEFAULT 0 )CHARSET=utf8 ; INSERT INTO `goods` VALUES(NULL,小风扇, 10.00, 1, 100); INSERT INTO `goods` VALUES(NULL,小台灯, 12.00, 1, 100); INSERT INTO `goods` VALUES(NULL,可口可乐, 3.00, 2, 100); -- taoBao表 CREATE TABLE `taoBao`( taoBao_money DOUBLE NOT NULL DEFAULT 0.0 )CHARSET=utf8 ; INSERT INTO `taoBao` VALUES(0);

2.ShopDao

package com.li.tx.hw.dao; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import javax.annotation.Resource; /** * @author 李 * @version 1.0 */ @Repository public class ShopDao { @Resource private JdbcTemplate jdbcTemplate; //通过商品id       ,查询商品价格 public Double queryGoodsPrice(int goodsId) { String sql = "SELECT price FROM goods WHERE goods_id=?"; return jdbcTemplate.queryForObject(sql, Double.class, goodsId); } //通过商品id     ,查询商品所属的卖家id public Integer queryGoodsOwner(int goodsId) { String sql = "SELECT seller_id FROM goods WHERE goods_id=?"; return jdbcTemplate.queryForObject(sql, Integer.class, goodsId); } //通过商品id                  ,修改商品库存量 public void updateGoodsNum(int goodsId, int shopNum) { String sql = "UPDATE goods SET goods_num=goods_num-? WHERE goods_id=?"; jdbcTemplate.update(sql, shopNum, goodsId); } //通过买家id          ,修改买家余额 public void updateBuyerMoney(Integer buyerId, Double money) { String sql = "UPDATE buyer SET buyer_money=buyer_money-? WHERE buyer_id=?"; jdbcTemplate.update(sql, money, buyerId); } //通过卖家id  ,修改卖家余额 public void updateSellerMoney(Integer sellerId, Double money) { String sql = "UPDATE seller SET seller_money=seller_money+? WHERE seller_id=?"; jdbcTemplate.update(sql, money, sellerId); } //修改 taoBao余额 public void updateTaobaoMoney(Double money) { String sql = "UPDATE taoBao SET taoBao_money=taoBao_money+?"; jdbcTemplate.update(sql, money); } }

3.ShopService

package com.li.tx.hw.service; import com.li.tx.hw.dao.ShopDao; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; /** * @author 李 * @version 1.0 */ @Service public class ShopService { @Resource private ShopDao shopDao; @Transactional public void shopping(int buyerId, int goodsId, int goodsNum) { System.out.println("用户购买信息 buyerId=" + buyerId + " goodsId=" + goodsId + " 购买数量=" + goodsNum); //查询商品价格 Double goodsPrice = shopDao.queryGoodsPrice(goodsId); System.out.println("商品价格=" + goodsPrice); //查询商品卖家 Integer sellerId = shopDao.queryGoodsOwner(goodsId); System.out.println("商品所属卖家=" + sellerId); //减少商品库存量 shopDao.updateGoodsNum(goodsId, goodsNum); System.out.println("商品库存-" + goodsNum); //修改买家余额 shopDao.updateBuyerMoney(buyerId, goodsPrice * goodsNum); System.out.println("买家余额-" + goodsPrice * goodsNum); //将成交额的 90% 转入卖家余额 shopDao.updateSellerMoney(sellerId, goodsPrice * goodsNum * 0.9); System.out.println("卖家余额+" + goodsPrice * goodsNum * 0.9); //将成交额的 10% 转入taoBao余额 shopDao.updateTaobaoMoney(goodsPrice * goodsNum * 0.1); System.out.println("taoBao余额+" + goodsPrice * goodsNum * 0.1); System.out.println("购买成功..."); } }

4.配置容器文件

<!--配置要扫描的包--> <context:component-scan base-package="com.li.tx.hw"/> <!--引入外部的属性文件--> <context:property-placeholder location="classpath:jdbc.properties"/> <!--配置数据源对象--> <bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSources"> <property name="user" value="${jdbc.user}"/> <property name="password" value="${jdbc.pwd}"/> <property name="driverClass" value="${jdbc.driver}"/> <property name="jdbcUrl" value="${jdbc.url}"/> </bean> <bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate"> <property name="dataSource" ref="dataSources"/> </bean> <!--配置事务管理器对象 1.DataSourceTransactionManager 这个对象是进行事务管理的 2.一定要配置数据源属性                 ,即指定该事务管理器 是对哪个数据源进行事务控制 --> <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="dataSourceTransactionManager"> <property name="dataSource" ref="dataSources"/> </bean> <!--配置:启用基于注解的声明式事务管理功能--> <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>

5.jdbc.properties

jdbc.user=root jdbc.pwd=123456 jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/spring

6.测试

@Test public void shoppingTest() { ApplicationContext ioc = new ClassPathXmlApplicationContext("shopping_ioc.xml"); ShopService shopService = ioc.getBean(ShopService.class); shopService.shopping(1, 1, 10); }

测试结果:

测试后的数据:

7.测试数据一致性:

修改sql             ,使其无法执行:

测试结果:出现异常                 。

查看数据库表,数据没有改变    。说明事务进行了回滚        。

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
vue父子组件间的参数传递是如何做到的(【Vue】父子组件通信)