Spring基于注解实现事务管理
在《Spring基于XML实现事务管理》一节中,我们通过 <tx:advice> 元素极大的简化了 Spring 声明式事务所需的 XML 配置。但其实我们还可以通过另一种方式进行进一步的简化,那就是“使用注解实现事务管理”。
在 Spring 中,声明式事务除了可以使用 XML 实现外,还可以使用注解实现,以进一步降低代码之间的耦合度。下面我们就来介绍下,通过注解是如何实现声明式事务管理。
<tx:annotation-driven> 元素的使用方式也十分的简单,我们只要在 Spring 的 XML 配置中添加这样一行配置即可。
与 <tx:advice> 元素一样,<tx:annotation-driven> 也需要通过 transaction-manager 属性来定义一个事务管理器,这个参数的取值默认为 transactionManager。如果我们使用的事务管理器的 id 与默认值相同,则可以省略对该属性的配置,形式如下。
通过 <tx:annotation-driven> 元素开启注解事务后,Spring 会自动对容器中的 Bean 进行检查,找到使用 @Transactional 注解的 Bean,并为其提供事务支持。
若 @Transactional 注解在类上使用,则表示类中的所有方法都支持事务;若 @Transactional 注解在方法上使用,则表示当前方法支持事务。
Spring 在容器中查找所有使用了 @Transactional 注解的 Bean,并自动为它们添加事务通知,通知的事务属性则是通过 @Transactional 注解的属性来定义的。
@Transactional 注解包含多个属性,其中常用属性如下表。
1. 在 MySQL 数据库中新建一个名为 spring-tx-db 的数据库实例,并在这个数据库中执行以下 SQL 语句。
通过以上 SQL 语句,我们共创建三张数据库表:order(订单表)、storage(商品库存表)、account(用户账户表)。
2. 新建一个名为 my-spring-tx-demo 的 Java 工程,并将以下依赖项导入到工程中。
3. 在 net.biancheng.c.entity 包下,创建一个名为 Order 的实体类,代码如下。
4. 在 net.biancheng.c.entity 包下,创建一个名为 Account 的实体类,代码如下。
4. 在 net.biancheng.c.entity 包下,创建一个名为 Storage 的实体类,代码如下。
5. 在 net.biancheng.net.dao 包下,创建一个名为 OrderDao 的接口,代码如下。
6. 在 net.biancheng.net.dao 包下,创建一个名为 AccountDao 的接口,代码如下。
7. 在 net.biancheng.net.dao 包下,创建一个名为 StorageDao 的接口,代码如下。
8. 在 net.biancheng.c.dao.impl 包下,创建 OrderDao 的实现类 OrderDaoImpl,代码如下。
9. 在 net.biancheng.c.dao.impl 包下,创建 AccountDao 的实现类 AccountDaoImpl,代码如下。
10. 在 net.biancheng.c.dao.impl 包下,创建 StorageDao 的实现类 StorageDaoImpl,代码如下。
11. 在 net.biancheng.c.service 包下,创建一个名为 OrderService 的接口,代码如下。
12. 在 net.biancheng.c.service.impl 包下,创建 OrderService 的实现类 OrderServiceImpl,代码如下。
13. 在 src 目录下,创建一个配置文件 jdbc.properties,配置内容如下。
14. 在 src 目录下创建一个 XML 配置文件 Beans.xml,配置内容如下。
15. 在 net.biancheng.c 包下,创建一个名为 MainApp 的类,代码如下。
16. 执行 MainApp 类中 main 方法,控制台输出如下。
17. 分别查看订单(order)表、商品库存(storage)表和账户(account)表中的数据,结果如下。
18. 再次执行 MainApp 中的 main 方法,控制台输出如下。
19. 再次对数据库表进行查询,发现三张数据库表都没有任何改变,说明在扣减账户发生异常后,事务回滚了。
在 Spring 中,声明式事务除了可以使用 XML 实现外,还可以使用注解实现,以进一步降低代码之间的耦合度。下面我们就来介绍下,通过注解是如何实现声明式事务管理。
1. 开启注解事务
tx 命名空间提供了一个 <tx:annotation-driven> 元素,用来开启注解事务,简化 Spring 声明式事务的 XML 配置。<tx:annotation-driven> 元素的使用方式也十分的简单,我们只要在 Spring 的 XML 配置中添加这样一行配置即可。
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
与 <tx:advice> 元素一样,<tx:annotation-driven> 也需要通过 transaction-manager 属性来定义一个事务管理器,这个参数的取值默认为 transactionManager。如果我们使用的事务管理器的 id 与默认值相同,则可以省略对该属性的配置,形式如下。
<tx:annotation-driven/>
通过 <tx:annotation-driven> 元素开启注解事务后,Spring 会自动对容器中的 Bean 进行检查,找到使用 @Transactional 注解的 Bean,并为其提供事务支持。
2. 使用 @Transactional 注解
@Transactional 注解是 Spring 声明式事务编程的核心注解,该注解既可以在类上使用,也可以在方法上使用。@Transactional public class XXX { @Transactional public void A(Order order) { …… } public void B(Order order) { …… } }
若 @Transactional 注解在类上使用,则表示类中的所有方法都支持事务;若 @Transactional 注解在方法上使用,则表示当前方法支持事务。
Spring 在容器中查找所有使用了 @Transactional 注解的 Bean,并自动为它们添加事务通知,通知的事务属性则是通过 @Transactional 注解的属性来定义的。
@Transactional 注解包含多个属性,其中常用属性如下表。
事务属性 | 说明 |
---|---|
propagation | 指定事务的传播行为。 |
isolation | 指定事务的隔离级别。 |
read-only | 指定是否为只读事务。 |
timeout | 表示超时时间,单位为“秒”;声明的事务在指定的超时时间后,自动回滚,避免事务长时间不提交会回滚导致的数据库资源的占用。 |
rollback-for | 指定事务对于那些类型的异常应当回滚,而不提交。 |
no-rollback-for | 指定事务对于那些异常应当继续运行,而不回滚。 |
示例 1
下面,我们就通过一个实例来演示下如何通过注解实现声明式事务,步骤如下。1. 在 MySQL 数据库中新建一个名为 spring-tx-db 的数据库实例,并在这个数据库中执行以下 SQL 语句。
DROP TABLE IF EXISTS `account`; CREATE TABLE `account` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id', `user_id` bigint DEFAULT NULL COMMENT '用户id', `total` decimal(10,0) DEFAULT NULL COMMENT '总额度', `used` decimal(10,0) DEFAULT NULL COMMENT '已用余额', `residue` decimal(10,0) DEFAULT '0' COMMENT '剩余可用额度', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; INSERT INTO `account` VALUES ('1', '1', '1000', '0', '1000'); DROP TABLE IF EXISTS `order`; CREATE TABLE `order` ( `id` bigint NOT NULL AUTO_INCREMENT, `order_id` varchar(200) NOT NULL, `user_id` varchar(200) NOT NULL COMMENT '用户id', `product_id` varchar(200) NOT NULL COMMENT '产品id', `count` int DEFAULT NULL COMMENT '数量', `money` decimal(11,0) DEFAULT NULL COMMENT '金额', `status` int DEFAULT NULL COMMENT '订单状态:0:创建中;1:已完结', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `storage`; CREATE TABLE `storage` ( `id` bigint NOT NULL AUTO_INCREMENT, `product_id` bigint DEFAULT NULL COMMENT '产品id', `total` int DEFAULT NULL COMMENT '总库存', `used` int DEFAULT NULL COMMENT '已用库存', `residue` int DEFAULT NULL COMMENT '剩余库存', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; INSERT INTO `storage` VALUES ('1', '1', '100', '0', '100');
通过以上 SQL 语句,我们共创建三张数据库表:order(订单表)、storage(商品库存表)、account(用户账户表)。
2. 新建一个名为 my-spring-tx-demo 的 Java 工程,并将以下依赖项导入到工程中。
- spring-beans-5.3.13.RELEASE.jar
- spring-context-5.3.13.RELEASE.jar
- spring-core-5.3.13.RELEASE.jar
- spring-expression-5.3.13.RELEASE.jar
- commons-logging-1.2.jar
- spring-jdbc-5.3.13.RELEASE.jar
- spring-tx-5.3.13.RELEASE.jar
- spring-aop-5.3.13.jar
- mysql-connector-java-8.0.23.jar
- aspectjweaver-1.9.7.jar
- spring-aspects-5.3.13.jar
3. 在 net.biancheng.c.entity 包下,创建一个名为 Order 的实体类,代码如下。
package net.biancheng.c.entity; import java.math.BigDecimal; public class Order { //自增 id private Long id; //订单 id private String orderId; //用户 id private String userId; //商品 id private String productId; //订单商品数量 private Integer count; //订单金额 private BigDecimal money; //订单状态 private Integer status; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getOrderId() { return orderId; } public void setOrderId(String orderId) { this.orderId = orderId; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getProductId() { return productId; } public void setProductId(String productId) { this.productId = productId; } public Integer getCount() { return count; } public void setCount(Integer count) { this.count = count; } public BigDecimal getMoney() { return money; } public void setMoney(BigDecimal money) { this.money = money; } public Integer getStatus() { return status; } public void setStatus(Integer status) { this.status = status; } }
4. 在 net.biancheng.c.entity 包下,创建一个名为 Account 的实体类,代码如下。
package net.biancheng.c.entity; import java.math.BigDecimal; public class Account { //自增 id private Long id; //用户 id private String userId; //账户总金额 private BigDecimal total; //已用账户金额 private BigDecimal used; //剩余账户金额 private BigDecimal residue; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public BigDecimal getTotal() { return total; } public void setTotal(BigDecimal total) { this.total = total; } public BigDecimal getUsed() { return used; } public void setUsed(BigDecimal used) { this.used = used; } public BigDecimal getResidue() { return residue; } public void setResidue(BigDecimal residue) { this.residue = residue; } }
4. 在 net.biancheng.c.entity 包下,创建一个名为 Storage 的实体类,代码如下。
package net.biancheng.c.entity; public class Storage { //自增 id private Long id; //商品 id private String productId; //商品库存总数 private Integer total; //已用商品数量 private Integer used; //剩余商品数量 private Integer residue; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getProductId() { return productId; } public void setProductId(String productId) { this.productId = productId; } public Integer getTotal() { return total; } public void setTotal(Integer total) { this.total = total; } public Integer getUsed() { return used; } public void setUsed(Integer used) { this.used = used; } public Integer getResidue() { return residue; } public void setResidue(Integer residue) { this.residue = residue; } }
5. 在 net.biancheng.net.dao 包下,创建一个名为 OrderDao 的接口,代码如下。
package net.biancheng.c.dao; import net.biancheng.c.entity.Order; public interface OrderDao { /** * 创建订单 * @param order * @return */ int createOrder(Order order); /** * 修改订单状态 * 将订单状态从未完成(0)修改为已完成(1) * @param orderId * @param status */ void updateOrderStatus(String orderId, Integer status); }
6. 在 net.biancheng.net.dao 包下,创建一个名为 AccountDao 的接口,代码如下。
package net.biancheng.c.dao; import net.biancheng.c.entity.Account; import java.math.BigDecimal; public interface AccountDao { /** * 根据用户查询账户金额 * @param userId * @return */ Account selectByUserId(String userId); /** * 扣减账户金额 * @param userId * @param money * @return */ int decrease(String userId, BigDecimal money); }
7. 在 net.biancheng.net.dao 包下,创建一个名为 StorageDao 的接口,代码如下。
package net.biancheng.c.dao; import net.biancheng.c.entity.Storage; public interface StorageDao { /** * 查询商品的库存 * @param productId * @return */ Storage selectByProductId(String productId); /** * 扣减商品库存 * @param record * @return */ int decrease(Storage record); }
8. 在 net.biancheng.c.dao.impl 包下,创建 OrderDao 的实现类 OrderDaoImpl,代码如下。
package net.biancheng.c.dao.impl; import net.biancheng.c.dao.OrderDao; import net.biancheng.c.entity.Order; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; @Repository public class OrderDaoImpl implements OrderDao { @Autowired private JdbcTemplate jdbcTemplate; @Override public int createOrder(Order order) { String sql = "insert into `order` (order_id,user_id, product_id, `count`, money, status) values (?,?,?,?,?,?)"; int update = jdbcTemplate.update(sql, order.getOrderId(), order.getUserId(), order.getProductId(), order.getCount(), order.getMoney(), order.getStatus()); return update; } @Override public void updateOrderStatus(String orderId, Integer status) { String sql = " update `order` set status = 1 where order_id = ? and status = ?;"; jdbcTemplate.update(sql, orderId, status); } }
9. 在 net.biancheng.c.dao.impl 包下,创建 AccountDao 的实现类 AccountDaoImpl,代码如下。
package net.biancheng.c.dao.impl; import net.biancheng.c.dao.AccountDao; import net.biancheng.c.entity.Account; import net.biancheng.c.entity.Order; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import java.math.BigDecimal; @Repository public class AccountDaoImpl implements AccountDao { @Autowired private JdbcTemplate jdbcTemplate; @Override public Account selectByUserId(String userId) { String sql = " select * from account where user_id = ?"; return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Account>(Account.class), userId); } @Override public int decrease(String userId, BigDecimal money) { String sql = "UPDATE account SET residue = residue - ?, used = used + ? WHERE user_id = ?;"; return jdbcTemplate.update(sql, money, money, userId); } }
10. 在 net.biancheng.c.dao.impl 包下,创建 StorageDao 的实现类 StorageDaoImpl,代码如下。
package net.biancheng.c.dao.impl; import net.biancheng.c.dao.StorageDao; import net.biancheng.c.entity.Storage; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; @Repository public class StorageDaoImpl implements StorageDao { @Autowired private JdbcTemplate jdbcTemplate; @Override public Storage selectByProductId(String productId) { String sql = "select * from storage where product_id = ?"; return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Storage>(Storage.class), productId); } @Override public int decrease(Storage record) { String sql = " update storage set used =? ,residue=? where product_id=?"; return jdbcTemplate.update(sql, record.getUsed(), record.getResidue(), record.getProductId()); } }
11. 在 net.biancheng.c.service 包下,创建一个名为 OrderService 的接口,代码如下。
package net.biancheng.c.service; import net.biancheng.c.entity.Order; public interface OrderService { /** * 创建订单 * @param order * @return */ public void createOrder(Order order); }
12. 在 net.biancheng.c.service.impl 包下,创建 OrderService 的实现类 OrderServiceImpl,代码如下。
package net.biancheng.c.service.impl; import net.biancheng.c.dao.AccountDao; import net.biancheng.c.dao.OrderDao; import net.biancheng.c.dao.StorageDao; import net.biancheng.c.entity.Account; import net.biancheng.c.entity.Order; import net.biancheng.c.entity.Storage; import net.biancheng.c.service.OrderService; 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; import java.text.SimpleDateFormat; import java.util.Date; @Service("orderService") public class OrderServiceImpl implements OrderService { @Autowired private OrderDao orderDao; @Autowired private AccountDao accountDao; @Autowired private StorageDao storageDao; /** * 在方法上使用 @Transactional 注解, * * @param order */ @Transactional(isolation = Isolation.DEFAULT, propagation = Propagation.REQUIRED, timeout = 10, readOnly = false) @Override public void createOrder(Order order) { //自动生成订单 id SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmssSSS"); String format = df.format(new Date()); String orderId = order.getUserId() + order.getProductId() + format; System.out.println("自动生成的订单 id 为:" + orderId); order.setOrderId(orderId); System.out.println("开始创建订单数据,订单号为:" + orderId); //创建订单数据 orderDao.createOrder(order); System.out.println("订单数据创建完成,订单号为:" + orderId); System.out.println("开始查询商品库存,商品 id 为:" + order.getProductId()); Storage storage = storageDao.selectByProductId(order.getProductId()); if (storage != null && storage.getResidue().intValue() >= order.getCount().intValue()) { System.out.println("商品库存充足,正在扣减商品库存"); storage.setUsed(storage.getUsed() + order.getCount()); storage.setResidue(storage.getTotal().intValue() - storage.getUsed()); int decrease = storageDao.decrease(storage); System.out.println("商品库存扣减完成"); } else { System.out.println("警告:商品库存不足,正在执行回滚操作!"); throw new RuntimeException("库存不足"); } System.out.println("开始查询用户的账户金额"); Account account = accountDao.selectByUserId(order.getUserId()); if (account != null && account.getResidue().intValue() >= order.getMoney().intValue()) { System.out.println("账户金额充足,正在扣减账户金额"); accountDao.decrease(order.getUserId(), order.getMoney()); System.out.println("账户金额扣减完成"); } else { System.out.println("警告:账户余额不足,正在执行回滚操作!"); throw new RuntimeException("账户余额不足"); } System.out.println("开始修改订单状态,未完成》》》》》已完成"); orderDao.updateOrderStatus(order.getOrderId(), 0); System.out.println("修改订单状态完成!"); } }
13. 在 src 目录下,创建一个配置文件 jdbc.properties,配置内容如下。
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/spring-tx-db jdbc.username=root jdbc.password=root
14. 在 src 目录下创建一个 XML 配置文件 Beans.xml,配置内容如下。
<?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-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!--开启注解事务--> <tx:annotation-driven/> <!--开启组件扫描--> <context:component-scan base-package="net.biancheng.c"></context:component-scan> <!--引入 jdbc.properties 中的配置--> <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder> <!--定义数据源 Bean--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <!--数据库连接地址--> <property name="url" value="${jdbc.url}"/> <!--数据库的用户名--> <property name="username" value="${jdbc.username}"/> <!--数据库的密码--> <property name="password" value="${jdbc.password}"/> <!--数据库驱动--> <property name="driverClassName" value="${jdbc.driver}"/> </bean> <!--定义 JdbcTemplate Bean--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!--将数据源的 Bean 注入到 JdbcTemplate 中--> <property name="dataSource" ref="dataSource"></property> </bean> <!--配置事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> </beans>
15. 在 net.biancheng.c 包下,创建一个名为 MainApp 的类,代码如下。
package net.biancheng.c; import net.biancheng.c.entity.Order; import net.biancheng.c.service.OrderService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import java.math.BigDecimal; public class MainApp { public static void main(String[] args) { ApplicationContext context2 = new ClassPathXmlApplicationContext("Beans.xml"); OrderService orderService = context2.getBean("orderService", OrderService.class); Order order = new Order(); //设置商品 id order.setProductId("1"); //商品数量 order.setCount(30); //商品金额 order.setMoney(new BigDecimal(600)); //设置用户 id order.setUserId("1"); //订单状态为未完成 order.setStatus(0); orderService.createOrder(order); } }
16. 执行 MainApp 类中 main 方法,控制台输出如下。
自动生成的订单 id 为:1120220111173635296 开始创建订单数据,订单号为:1120220111173635296 订单数据创建完成,订单号为:1120220111173635296 开始查询商品库存,商品 id 为:1 商品库存充足,正在扣减商品库存 商品库存扣减完成 开始查询用户的账户金额 账户金额充足,正在扣减账户金额 账户金额扣减完成 开始修改订单状态,未完成》》》》》已完成 修改订单状态完成!
17. 分别查看订单(order)表、商品库存(storage)表和账户(account)表中的数据,结果如下。
id | order_id | user_id | product_id | count | money | status |
---|---|---|---|---|---|---|
1 | 1120220111173635296 | 1 | 1 | 30 | 600 | 1 |
订单(order)表
id | product_id | total | used | residue |
---|---|---|---|---|
1 | 1 | 100 | 30 | 70 |
商品库存(storage)表
id | user_id | total | used | residue |
---|---|---|---|---|
1 | 1 | 1000 | 600 | 400 |
账户(account)表
自动生成的订单 id 为:1120220111175556986 开始创建订单数据,订单号为:1120220111175556986 订单数据创建完成,订单号为:1120220111175556986 开始查询商品库存,商品 id 为:1 商品库存充足,正在扣减商品库存 商品库存扣减完成 开始查询用户的账户金额 警告:账户余额不足,正在执行回滚操作! Exception in thread "main" java.lang.RuntimeException: 账户余额不足 at net.biancheng.c.service.impl.OrderServiceImpl.createOrder(OrderServiceImpl.java:61) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344) at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215) at com.sun.proxy.$Proxy13.createOrder(Unknown Source) at net.biancheng.c.MainApp.main(MainApp.java:25)
19. 再次对数据库表进行查询,发现三张数据库表都没有任何改变,说明在扣减账户发生异常后,事务回滚了。