Spring基于XML实现事务管理
Spring基于XML实现事务管理
Spring 声明式事务管理是通过 AOP 实现的,其本质是对方法前后进行拦截,然后在目标方法开始之前创建(或加入)一个事务,在执行完目标方法后,根据执行情况提交或者回滚事务。
声明式事务最大的优点就是对业务代码的侵入性低,可以将业务代码和事务管理代码很好地进行解耦。
Spring 实现声明式事务管理主要有 2 种方式:
- 基于 XML 方式的声明式事务管理。
- 通过 Annotation 注解方式的事务管理。
下面介绍如何通过 XML 的方式实现声明式事务管理,步骤如下。
1. 引入 tx 命名空间
Spring 提供了一个 tx 命名空间,借助它可以极大地简化 Spring 中的声明式事务的配置。
想要使用 tx 命名空间,第一步就是要在 XML 配置文件中添加 tx 命名空间的约束。
1 |
|
**注意:**由于 Spring 提供的声明式事务管理是依赖于 Spring AOP 实现的,因此我们在 XML 配置文件中还应该添加与 aop 命名空间相关的配置。
2. 配置事务管理器
接下来,我们就需要借助数据源配置,定义相应的事务管理器实现(PlatformTransactionManager 接口的实现类)的 Bean,配置内容如下。
1 | <!--配置数据源 --> |
在以上配置中,配置的事务管理器实现为 DataSourceTransactionManager,即为 JDBC 和 iBatis 提供的 PlatformTransactionManager 接口实现。
3. 配置事务通知
在 Spring 的 XML 配置文件中配置事务通知,指定事务作用的方法以及所需的事务属性。
1 | <!--配置通知--> |
事务管理器配置
当我们使用 tx:advice 来声明事务时,需要通过 transaction-manager 参数来定义一个事务管理器,这个参数的取值默认为 transactionManager。
如果我们自己设置的事务管理器(第 2 步中设置的事务管理器 id)恰好与默认值相同,则可以省略对改参数的配置。
1 | <tx:advice id="tx-advice" > |
但如果我们自己设置的事务管理器 id 与默认值不同,则必须手动在 tx:advice 元素中通过 transaction-manager 参数指定。
事务属性配置
对于tx:advice 来说,事务属性是被定义在tx:attributes 中的,该元素可以包含一个或多个 tx:method 元素。
tx:method 元素包含多个属性参数,可以为某个或某些指定的方法(name 属性定义的方法)定义事务属性,如下表所示。
事务属性 | 说明 |
---|---|
propagation | 指定事务的传播行为。 |
isolation | 指定事务的隔离级别。 |
read-only | 指定是否为只读事务。 |
timeout | 表示超时时间,单位为“秒”;声明的事务在指定的超时时间后,自动回滚,避免事务长时间不提交会回滚导致的数据库资源的占用。 |
rollback-for | 指定事务对于那些类型的异常应当回滚,而不提交。 |
no-rollback-for | 指定事务对于那些异常应当继续运行,而不回滚。 |
4. 配置切点切面
tx:advice 元素只是定义了一个 AOP 通知,它并不是一个完整的事务性切面。我们在 tx:advice 元素中并没有定义哪些 Bean 应该被通知,因此我们需要一个切点来做这件事。
在 Spring 的 XML 配置中,我们可以利用 Spring AOP 技术将事务通知(tx-advice)和切点配置到切面中,配置内容如下。
1 | <!--配置切点和切面--> |
在以上配置中用到了 aop 命名空间,这就是我们为什么在给工程导入依赖时要引入 spring-aop 等 Jar 包的原因。
示例
下面,我们就通过一个实例来演示下如何通过 XML 配置事务管理,步骤如下。
1. 在 MySQL 数据库中新建一个名为 spring-tx-db 的数据库实例,并在这个数据库中执行以下 SQL 语句。
1 | DROP TABLE IF EXISTS `account`; |
通过以上 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 的实体类,代码如下。
1 | package net.biancheng.c.entity; |
4.在 net.biancheng.c.entity 包下,创建一个名为 Account 的实体类,代码如下。
1 | package net.biancheng.c.entity; |
5.在 net.biancheng.c.entity 包下,创建一个名为 Storage 的实体类,代码如下。
1 | package net.biancheng.c.dao; |
6.在 net.biancheng.net.dao 包下,创建一个名为 OrderDao 的接口,代码如下。
1 | package net.biancheng.c.dao; |
7.在 net.biancheng.net.dao 包下,创建一个名为 AccountDao 的接口,代码如下。
1 | package net.biancheng.c.dao; |
8.在 net.biancheng.net.dao 包下,创建一个名为 StorageDao 的接口,代码如下。
1 | 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);} |
9.在 net.biancheng.c.dao.impl 包下,创建 OrderDao 的实现类 OrderDaoImpl,代码如下。
1 | 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;@Repositorypublic 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,代码如下。
1 | 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;@Repositorypublic 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,代码如下。
1 | 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;@Repositorypublic 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()); }} |
- 在 net.biancheng.c.service 包下,创建一个名为 OrderService 的接口,代码如下。
1 | package net.biancheng.c.service;import net.biancheng.c.entity.Order;public interface OrderService { /** * 创建订单 * @param order * @return */ public void createOrder(Order order);} |
- 在 net.biancheng.c.service.impl 包下,创建 OrderService 的实现类 OrderServiceImpl,代码如下。
1 | 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 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; @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("修改订单状态完成!"); }} |
- 在 src 目录下,创建一个 jdbc.properties,配置内容如下。
1 | jdbc.driver=com.mysql.cj.jdbc.Driver |
- 在 src 目录下,创建一个 Spring 的 XML 配置文件 Beans.xml,配置内容如下。
1 | <?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:aop="http://www.springframework.org/schema/aop" 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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!--开启组件扫描--> <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> <!--配置通知--> <tx:advice id="tx-advice"> <!--配置事务参数--> <tx:attributes> <!--name 指定哪些方法上添加事务--> <tx:method name="create*" propagation="REQUIRED" isolation="DEFAULT" read-only="false" timeout="10"/> </tx:attributes> </tx:advice> <!--配置切点和切面--> <aop:config> <!--配置切点--> <aop:pointcut id="tx-pt" expression="execution(* net.biancheng.c.service.impl.OrderServiceImpl.*(..))"/> <!--配置切面--> <aop:advisor advice-ref="tx-advice" pointcut-ref="tx-pt"></aop:advisor> </aop:config></beans> |
- 在 net.biancheng.c 包下,创建一个名为 MainApp 的类,代码如下。
1 | 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"); //商品数量为 30 order.setCount(30); //商品金额为 600 order.setMoney(new BigDecimal(600)); //设置用户 id order.setUserId("1"); //订单状态为未完成 order.setStatus(0); orderService.createOrder(order); }} |
- 执行 MainApp 类中 main 方法,控制台输出如下。
1 | 自动生成的订单 id 为:1120220111173635296 |
- 分别查看订单(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)表
\18. 再次执行 MainApp 中的 main 方法,控制台输出如下。
1 | 自动生成的订单 id 为:1120220111175556986 |