在《Spring基于XML实现事务管理》一节中,我们通过 tx:advice 元素极大的简化了 Spring 声明式事务所需的 XML 配置。但其实我们还可以通过另一种方式进行进一步的简化,那就是“使用注解实现事务管理”。
在 Spring 中,声明式事务除了可以使用 XML 实现外,还可以使用注解实现,以进一步降低代码之间的耦合度。下面我们就来介绍下,通过注解是如何实现声明式事务管理。
- 开启注解事务
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 注解包含多个属性,其中常用属性如下表。
示例 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(用户账户表)。
-
新建一个名为 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 -
在 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;
}
} -
在 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;
}
} -
在 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;
}
} -
在 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);
}
-
在 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);
}
-
在 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);
}
-
在 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 intoorder
(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 = " updateorder
set status = 1 where order_id = ? and status = ?;";
jdbcTemplate.update(sql, orderId, status);
}
} -
在 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.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);
}
} -
在 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.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 的接口,代码如下。
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,代码如下。
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());