在同一个类下的两个方法中
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
@Resource
private ISeckillVoucherService seckillVoucherService;
@Resource
private RedisIdWorker redisIdWorker;
// 添加此行,引入 ApplicationContext
@Autowired
private ApplicationContext applicationContext;
/**
* 秒杀优惠券
* @param voucherId
* @return
*/
@Override
//@Transactional 这个方法不用事物注解是为了不让粒度太大
public Result seckillVoucher(Long voucherId) {
// 1.查询优惠券
SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
// 2.判断秒杀是否开始
if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
// 尚未开始
return Result.fail("秒杀尚未开始!");
}
// 3.判断秒杀是否已经结束
if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
// 尚未开始
return Result.fail("秒杀已经结束!");
}
// 4.判断库存是否充足
if (voucher.getStock() < 1) {
// 库存不足
return Result.fail("库存不足!");
}
/**
*用户锁,防止同一个用户秒杀太多订单.这里的intern()是去作用是将字符串添加到
*Java 内存中的字符串常量池(String Pool)中,并返回该常量池中已存在的相同字符串引用
*(如果存在的话),这样才能保证用户ID的锁是唯一锁,要是只是userId.toString(),返回的是
*一个new的字符串对象,这会导致同一个ID也会开启开启不同的锁,导致锁失效
*这里的锁是单个jvm的,在分布式系统中会失效
*/
Long userId = UserHolder.getUser().getId();
//1.这里是实现不了createVoucherOrder方法的事物的
//如果 seckillVoucher 方法没有 @Transactional 注解,那么 createVoucherOrder 方法的事务控制也不会生效,整个秒杀流程将不受事务保护。
//当 seckillVoucher 方法没有 @Transactional 注解时,意味着该方法本身并不受 Spring 事务管理器的控制,不会开启任何事务。此时,如果 seckillVoucher 方法内部调用 createVoucherOrder 方法,尽管 createVoucherOrder 方法带有 @Transactional 注解,但由于以下原因,事务控制不会生效:
//Spring AOP 代理机制: Spring AOP 通过代理模式来实现对方法的增强,包括事务管理。当一个方法被标注为 @Transactional 时,Spring 会在运行时为该方法对应的 Bean 创建一个代理对象。当外部代码通过 Spring 容器调用这个 Bean 的方法时,实际上是调用了代理对象的方法。代理对象在执行目标方法前会先开启一个事务,执行完目标方法后根据事务的执行情况决定提交或回滚事务。
//然而,对于同一个类中的方法调用,由于 Java 引用直接指向的是原始对象而非代理对象,所以在 seckillVoucher 方法内部调用 createVoucherOrder 方法时,实际上是直接调用了原始对象的方法,没有经过代理对象的中介。因此,Spring 事务管理器无法介入,createVoucherOrder 方法的事务控制也就不会生效。
//简而言之: 由于 seckillVoucher 方法没有 @Transactional 注解,不会开启事务,同时它直接调用 createVoucherOrder 方法时没有经过 Spring AOP 代理的封装,所以 createVoucherOrder 方法的事务控制不会生效。整个秒杀流程将不受任何事务保护,可能导致数据一致性问题。例如,如果在创建订单过程中出现异常,已扣减的库存可能不会恢复,或者在数据库写入失败时,用户已经收到了订单成功的响应。
// 要使 createVoucherOrder 方法的事务控制生效,需确保其调用路径经过 Spring AOP 代理,如将其移至单独的类中,或在 seckillVoucher 方法中使用 TransactionTemplate 或 PlatformTransactionManager 手动控制事务,或使用 AspectJ 提供的更精细的织入支持。
synchronized (userId.toString().intern()){
return createVoucherOrder(voucherId);
}
//---------------------------------------------------------------------------------------分界线
//2.这通过自注入来解决
//首先,添加 ApplicationContext 类型的成员变量,并使用 @Autowired 注解自动注入 Spring 应用上下文。
//然后,在 seckillVoucher 方法中,通过 applicationContext.getBean(VoucherOrderServiceImpl.class) 获取当前类(VoucherOrderServiceImpl)的代理对象。由于当前类中存在 @Transactional 注解的方法,Spring 会为其创建代理对象。通过这种方式获取的代理对象具备事务管理能力。
//最后,使用代理对象调用 createVoucherOrder 方法。
// 使用 ApplicationContext 获取当前类的代理对象,并通过代理对象调用 createVoucherOrder 方法
ynchronized (userId.toString().intern()){
VoucherOrderServiceImpl proxy = (VoucherOrderServiceImpl) applicationContext.getBean(VoucherOrderServiceImpl.class);
return proxy.createVoucherOrder(voucherId);
}
//3.通过AopContext来解决
//还可以通过 AopContext 来解决同一个类内部方法调用事务失效的问题。AopContext 是 Spring AOP 框架提供的一个工具类,用于在切面内部访问当前代理对象。在 seckillVoucher 方法中,您可以使用 AopContext.currentProxy() 获取当前类的代理对象,然后通过代理对象调用 createVoucherOrder 方法,以确保事务管理的有效性。
//但还需要额外做两步,在最下面
synchronized (userId.toString().intern()){
IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
return proxy.createVoucherOrder(voucherId);
}
}
//被调用的秒杀方法
@Transactional
public Result createVoucherOrder(Long voucherId) {
Long userId = UserHolder.getUser().getId();
synchronized(userId.toString().intern()){
// 5.1.查询订单
int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
// 5.2.判断是否存在
if (count > 0) {
// 用户已经购买过了
return Result.fail("用户已经购买过一次!");
}
// 6.扣减库存
boolean success = seckillVoucherService.update()
.setSql("stock = stock - 1") // set stock = stock - 1
.eq("voucher_id", voucherId).gt("stock", 0) // where id = ? and stock > 0
.update();
if (!success) {
// 扣减失败
return Result.fail("库存不足!");
}
// 7.创建订单
VoucherOrder voucherOrder = new VoucherOrder();
// 7.1.订单id
long orderId = redisIdWorker.nextId("order");
voucherOrder.setId(orderId);
// 7.2.用户id
voucherOrder.setUserId(userId);
// 7.3.代金券id
voucherOrder.setVoucherId(voucherId);
save(voucherOrder);
// 7.返回订单id
return Result.ok(orderId);
}
}
}
pom中加入注解
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
启动类上加入@EnableAspectJAutoProxy注解来暴露代理对象
@EnableAspectJAutoProxy(exposeProxy = true)//暴露代理对象
@SpringBootApplication
public class HmDianPingApplication {
public static void main(String[] args) {
SpringApplication.run(HmDianPingApplication.class, args);
}
}
最后2.的自注入也可以这样
private final IVoucherOrderService self;
@Autowired
public VoucherOrderServiceImpl(IVoucherOrderService self) {
this.self = self;
}
// ...
@Override
public Result seckillVoucher(Long voucherId) {
// ... 其他逻辑 ...
return self.createVoucherOrder(voucherId);
}
@Transactional
public Result createVoucherOrder(Long voucherId) {
// ... 秒杀下单逻辑 ...
}