首页IT科技简述声明式事务管理的处理方式和特点(day15-声明式事务)

简述声明式事务管理的处理方式和特点(day15-声明式事务)

时间2025-05-05 12:20:54分类IT科技浏览3845
导读:声明式事务 1.事务分类 编程式事务...

声明式事务

1.事务分类

编程式事务 Connection connection = JdbcUtils.getConnection(); try{ //1.先设置事务不要提交 connection.setAutoCommit(false); //2.进行业务 crud //3.提交事务 connection.commit(); }catch(Exception e){ //4.出现异常            ,回滚 connection.rollback(); } 声明式事务(后面以一个购买商品的系统为例)

2.声明式事务-使用实例

2.1需求说明

需求说明 - 用户购买商品

去处理用户购买商品的业务逻辑:当一个用户去购买商品                ,应该包含三个步骤:

通过商品 id 获取价格 购买商品(某人购买商品     ,修改用户余额) 修改库存量

这里一共涉及到三张表:用户表            、商品表                、商品存量表            。显然            ,应该使用事务处理                。

2.2解决方案分析

方案一:使用传统的编程式事务来处理                 ,将代码写到一起

(缺点是:代码冗余     ,效率低      ,不利于拓展;优点是简单                 ,好理解)

//例如: Connection connection = JdbcUtils.getConnection(); try{ //1.先设置事务不要提交 connection.setAutoCommit(false); //2.进行业务 crud //多个表的修改           ,添加      ,删除 //select form 商品表 => 获取价格 //修改用户余额 update... //修改商品库存量 update... //3.提交事务 connection.commit(); }catch(Exception e){ //4.出现异常                 ,回滚 connection.rollback(); }

方案二:使用 Spring 的声明式事务来处理           ,可以将上面三个子步骤分别写成一个方法,然后统一管理     。

(这是Spring的优越性所在                 ,开发中使用很多                ,优点是无代码冗余,效率高            ,拓展方便                ,缺点是理解较困难)底层使用AOP(动态代理+动态绑定+反射+注解)

2.3声明式事务使用-代码实现

创建表 -- 演示声明式事务创建的表 -- 用户表 CREATE TABLE `user_account`( user_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, user_name VARCHAR(32) NOT NULL DEFAULT , money DOUBLE NOT NULL DEFAULT 0.0 )CHARSET=utf8; INSERT INTO `user_account` VALUES(NULL,张三, 1000); INSERT INTO `user_account` VALUES(NULL,李四, 2000); -- 商品表 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 )CHARSET=utf8 ; INSERT INTO `goods` VALUES(NULL,小风扇, 10.00); INSERT INTO `goods` VALUES(NULL,小台灯, 12.00); INSERT INTO `goods` VALUES(NULL,可口可乐, 3.00); -- 商品存量表 CREATE TABLE `goods_amount`( goods_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, goods_num INT UNSIGNED DEFAULT 0 )CHARSET=utf8 ; INSERT INTO `goods_amount` VALUES(1,200); INSERT INTO `goods_amount` VALUES(2,20); INSERT INTO `goods_amount` VALUES(3,15); 创建GoodsDao 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); } } 配置容器文件

因为使用了注解 @Resource 的方式自动装配 JdbcTemplate 对象            ,这里需要配置该对象            。

<!--配置要扫描的包--> <context:component-scan base-package="com.li.tx"/> <!--引入外部的属性文件--> <context:property-placeholder location="classpath:jdbc.properties"/> <!--配置数据源对象-DataSource--> <bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource"> <!--给数据源对象配置属性值--> <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> <!--配置JdbcTemplate对象--> <bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate"> <!--给JdbcTemplate对象配置DataSource属性--> <property name="dataSource" ref="dataSource"/> </bean> 创建GoodsService                 ,编写方法     ,验证不使用事务就会出现数据不一致现象 package com.li.tx.service; import com.li.tx.dao.GoodsDao; import org.springframework.stereotype.Service; import javax.annotation.Resource; /** * @author 李 * @version 1.0 */ @Service //将GoodsService对象注入到容器中 public class GoodsService { @Resource private GoodsDao goodsDao; /** * 编写一个方法      ,完成用户购买商品的业务 * * @param userId 用户 id * @param goodsId 商品 id * @param amount 购买的商品数量 */ public void buyGoods(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("用户购买成功..."); } } 新增添扫描的包 <context:component-scan base-package="com.li.tx.service"/> 为了测试                 ,故意在Dao的sql语句中添加错误符号

测试:

@Test public void buyGoodsTest() { ApplicationContext ioc = new ClassPathXmlApplicationContext("tx.xml"); GoodsService goodsService = ioc.getBean(GoodsService.class); goodsService.buyGoods(1,1,10); }

测试结果:出现异常

原始表信息:

当前表信息:

可以看到用户表的余额减少了           ,但是商品库存表的库存没有改变                 。这就产生了数据不一致问题      ,因此要使用事务     。

改进GoodsService的业务方法                 ,使用声明式事务: /** * 1.使用注解 @Transactional 可以进行声明式事务控制 * 2.该注解会将标识方法中           ,对数据库的操作 作为一个事务来管理 * 3.@Transactional 底层是使用的仍然是AOP机制 * 4.底层是使用动态代理对象来调用 buyGoodsByTx()方法 * 5.在执行 buyGoodsByTx()方法前,先调用事务管理器的 doBegin()方法                 ,再调用目标方法 * 如果执行没有发生异常                ,就调用事务管理器 doCommit()方法,否则调用 doRollback()方法 * @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("用户购买成功..."); } 之前的基础上            ,在容器文件中配置事务管理器                ,并启用基于注解的声明式事务管理功能 <!--配置事务管理器-对象 1.DataSourceTransactionManager 这个对象是进行事务管理的 2.一定要配置数据源属性     ,即指定该事务管理器 是对哪个数据源进行事务控制 --> <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!--配置:启用基于注解的声明式事务管理功能--> <tx:annotation-driven transaction-manager="transactionManager"/>

注意:这里的 annotation-driven 标签要选择以tx结尾的

再次测试 @Test public void buyGoodsTestByTx() { ApplicationContext ioc = new ClassPathXmlApplicationContext("tx.xml"); GoodsService goodsService = ioc.getBean(GoodsService.class); goodsService.buyGoodsByTx(1,1,10); }

测试结果:可以看到仍然出现异常(因为之前在sql语句中故意添加了错误字符)

测试前数据:

测试后数据:

表数据在测试前后数据一致      。这说明事务控制起作用了            ,在出现异常时进行了回滚                 ,因此数据没有被改变                 。

2.4声明式事务机制-Debug

在整个声明式事务中     ,DataSourceTransactionManager类尤为重要           。

我们可以看到在 DataSourceTransactionManager 的源码中      ,有一个 DataSource 属性                 ,即数据源对象      。因为连接是在 DataSource 中获取           ,而事务管理器通过连接才能进行事务管理                 。

此外      ,DataSourceTransactionManager 还有很多重要的方法:doBegin()                 ,doCommit()           ,doRollback()等           。

debug-1-异常情况

以 2.3 的代码为例,在doBegin方法旁打上断点。

debug测试方法:buyGoodsTestByTx()

@Test public void buyGoodsTestByTx() { ApplicationContext ioc = new ClassPathXmlApplicationContext("tx.xml"); GoodsService goodsService = ioc.getBean(GoodsService.class); goodsService.buyGoodsByTx(1,1,10); }

光标首先跳转到doBegin方法                 ,点击Step Over                ,当运行到下面的代码时,可以看到con.getAutoCommit() 的值为true            ,即此时事务默认自动提交:

继续点击Step Over                ,当运行了 con.setAutoCommit(false); 后     ,可以看到 con.getAutoCommit() 的值变成了false            ,此时事务不再进行自动提交:

在GoodsService的方法旁添加第二个断点                 ,点击 Resume Program

光标跳转到第二个断点处     ,说明程序是先执行了doBegin()方法      ,再执行的bugGoodsByTx()方法                 。

在事务管理器的doRollback方法中打上第三个断点

继续点击step Over                 ,当bugGoodsByTx()方法执行到 goodsDao.updateAmount(goodsId, amount); 时           ,光标跳转到了第三个断点处!最终在该方法中      ,执行了 con.rollback()                 ,进行回滚                。

debug-2-正常的流程

修改之前的sql语句           ,将其变回正确的SQL。在事务管理器的doCommit方法中添加断点,然后点击debug            。

光标仍然先进入到doBegin方法中                 ,将自动事务提交修改为false后                ,又调转到目标方法                。这次执行完目标方法后,光标跳转到了doCommit()方法中     。在没有出现异常的情况下            ,执行了事务提交            。

总结:

在执行目标方法 buyGoodsByTx() 前                ,先调用事务管理器的 doBegin() 方法     ,再调用目标方法                 。如果执行没有发生异常            ,就调用事务管理器 doCommit()方法                 ,否则调用 doRollback()方法     。

3.事务的传播机制

事务的传播机制说明:

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

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

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

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

3.1事务传播机制种类

事务传播的属性 / 种类:

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

常用的就是前面两种:(1)REQUIRED,(2)REQUIRES_NEWREQUIRES_NEW

其他的不常用

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

展开全文READ MORE
ps中复制文字的快捷键是什么(ps文字复制粘贴移动) 2345怎么样安全卫士(教你3招轻松在2345安全卫士有效进行系统防护)