Spring 基于注解的事务管理
数据库事务概念
事务(Transaction)是基于关系型数据库(RDBMS)的企业应用的重要组成部分。在软件开发领域,事务扮演者十分重要的角色,用来确保应用程序数据的完整性和一致性。
事务具有 4 个特性:原子性、一致性、隔离性和持久性,简称为 ACID 特性。
- 原子性(Atomicity):一个事务是一个不可分割的工作单位,事务中包括的动作要么都做要么都不做。
- 一致性(Consistency):事务必须保证数据库从一个一致性状态变到另一个一致性状态,一致性和原子性是密切相关的。
- 隔离性(Isolation):一个事务的执行不能被其它事务干扰,即一个事务内部的操作及使用的数据对并发的其它事务是隔离的,并发执行的各个事务之间不能互相打扰。
- 持久性(Durability):持久性也称为永久性,指一个事务一旦提交,它对数据库中数据的改变就是永久性的,后面的其它操作和故障都不应该对其有任何影响。
spring 事务管理
开启事务
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,并为其提供事务支持。
使用 @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 注解包含多个属性,其中常用属性如下表。
spring 事务使用实例
首先创建三张表 order(订单表)、storage(商品库存表)、account(用户账户表)
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');
事务管理需要依赖的包
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
创建一个名为 Order 的实体类,代码如下。
package net.test.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;
//get set方法省略
}
创建一个名为 Account 的实体类,代码如下。
package net.test.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;
//get set方法省略
}
创建一个名为 Storage 的实体类,代码如下。
package net.test.entity;
public class Storage {
//自增 id
private Long id;
//商品 id
private String productId;
//商品库存总数
private Integer total;
//已用商品数量
private Integer used;
//剩余商品数量
private Integer residue;
// get set方法省略
}
创建一个名为 OrderDao 的接口,代码如下。
package net.test.dao;
import net.test.entity.Order;
public interface OrderDao {
/**
* 创建订单
* @param order
* @return
*/
int createOrder(Order order);
/**
* 修改订单状态
* 将订单状态从未完成(0)修改为已完成(1)
* @param orderId
* @param status
*/
void updateOrderStatus(String orderId, Integer status);
}
创建一个名为 AccountDao 的接口,代码如下。
package net.test.dao;
import net.test.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);
}
创建一个名为 StorageDao 的接口,代码如下。
package net.test.dao;
import net.test.entity.Storage;
public interface StorageDao {
/**
* 查询商品的库存
* @param productId
* @return
*/
Storage selectByProductId(String productId);
/**
* 扣减商品库存
* @param record
* @return
*/
int decrease(Storage record);
}
创建 OrderDao 的实现类 OrderDaoImpl,代码如下。
package net.test.dao.impl;
import net.test.dao.OrderDao;
import net.test.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);
}
}
创建 AccountDao 的实现类 AccountDaoImpl,代码如下。
package net.test.dao.impl;
import net.test.dao.AccountDao;
import net.test.entity.Account;
import net.test.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);
}
}
创建 StorageDao 的实现类 StorageDaoImpl,代码如下。
package net.test.dao.impl;
import net.test.dao.StorageDao;
import net.test.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());
}
}
创建一个名为 OrderService 的接口,代码如下。
package net.test.service;
import net.test.entity.Order;
public interface OrderService {
//创建订单
public void createOrder(Order order);
}
创建 OrderService 的实现类 OrderServiceImpl,代码如下。
package net.test.service.impl;
import net.test.dao.AccountDao;
import net.test.dao.OrderDao;
import net.test.dao.StorageDao;
import net.test.entity.Account;
import net.test.entity.Order;
import net.test.entity.Storage;
import net.test.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 注解
*/
@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("修改订单状态完成!");
}
}
在 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
在 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.test"></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>
创建一个名为 MainApp 的类,代码如下。
package net.test;
import net.test.entity.Order;
import net.test.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);
}
}
执行 MainApp 类中 main 方法,控制台输出如下。
自动生成的订单 id 为:1120220111173635296
开始创建订单数据,订单号为:1120220111173635296
订单数据创建完成,订单号为:1120220111173635296
开始查询商品库存,商品 id 为:1
商品库存充足,正在扣减商品库存
商品库存扣减完成
开始查询用户的账户金额
账户金额充足,正在扣减账户金额
账户金额扣减完成
开始修改订单状态,未完成》》》》》已完成
修改订单状态完成!