预留库存的实现
1. 实体类
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;import java.sql.Timestamp;@Data
@TableName("products")
public class Product {private Long id;private String name;private int stock;
}@Data
@TableName("shopping_carts")
public class ShoppingCart {private Long id;private Long userId;private Long productId;private int quantity;private Timestamp reservedTime;
}
2. Mapper 接口
在 ProductMapper 中添加自定义的库存更新方法:
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Update;@Mapper
public interface ProductMapper extends BaseMapper<Product> {@Update("update products set stock = stock - #{quantity} where id = #{productId} and stock >= #{quantity}")int reduceStock(Long productId, int quantity);@Update("update products set stock = stock + #{quantity} where id = #{productId}")int increaseStock(Long productId, int quantity);
}@Mapper
public interface ShoppingCartMapper extends BaseMapper<ShoppingCart> {java.util.List<ShoppingCart> selectByReservedTimeBefore(java.sql.Timestamp timestamp);
}
3. Mapper XML 文件(可选)
ShoppingCartMapper.xml 文件保持不变:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.ShoppingCartMapper"><select id="selectByReservedTimeBefore" resultType="com.example.demo.entity.ShoppingCart">SELECT * FROM shopping_carts WHERE reserved_time < #{timestamp}</select>
</mapper>
4. 服务类
修改 ShoppingCartService 中的 addToCart 和 releaseExpiredReservations 方法:
import com.example.demo.entity.Product;
import com.example.demo.entity.ShoppingCart;
import com.example.demo.mapper.ProductMapper;
import com.example.demo.mapper.ShoppingCartMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.sql.Timestamp;
import java.util.Calendar;
import java.util.List;@Service
public class ShoppingCartService {@Autowiredprivate ProductMapper productMapper;@Autowiredprivate ShoppingCartMapper shoppingCartMapper;@Transactionalpublic void addToCart(Long userId, Long productId, int quantity) {int updatedRows = productMapper.reduceStock(productId, quantity);if (updatedRows == 0) {throw new RuntimeException("Insufficient stock");}ShoppingCart shoppingCart = new ShoppingCart();shoppingCart.setUserId(userId);shoppingCart.setProductId(productId);shoppingCart.setQuantity(quantity);shoppingCart.setReservedTime(new Timestamp(System.currentTimeMillis()));shoppingCartMapper.insert(shoppingCart);}@Transactionalpublic void releaseExpiredReservations() {Calendar calendar = Calendar.getInstance();calendar.add(Calendar.MINUTE, -15);Timestamp expiredTime = new Timestamp(calendar.getTimeInMillis());List<ShoppingCart> expiredCarts = shoppingCartMapper.selectByReservedTimeBefore(expiredTime);for (ShoppingCart cart : expiredCarts) {try {int updatedRows = productMapper.increaseStock(cart.getProductId(), cart.getQuantity());if (updatedRows == 0) {System.err.println("Failed to increase stock for product id: " + cart.getProductId());}shoppingCartMapper.deleteById(cart.getId());} catch (Exception e) {System.err.println("Error releasing reservation for cart id: " + cart.getId() + ", error: " + e.getMessage());}}}
}
这里使用悲观锁的思想,利用mysql数据库在update,insert,delete时对应数据加上行锁的特性解决并发冲突,保证了在更新数据时,数据是最新的,且其它update,insert,delete操作被阻塞,乐观锁与悲观锁的具体参考文章“乐观锁与悲观锁的使用场景”。
5. 控制器
ShoppingCartController 保持不变:
import com.example.demo.service.ShoppingCartService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;@RestController
public class ShoppingCartController {@Autowiredprivate ShoppingCartService shoppingCartService;@PostMapping("/addToCart")public String addToCart(@RequestParam Long userId, @RequestParam Long productId, @RequestParam int quantity) {try {shoppingCartService.addToCart(userId, productId, quantity);return "Product added to cart successfully";} catch (Exception e) {return e.getMessage();}}
}
6. 定时任务
ReservationExpirationTask 保持不变:
import com.example.demo.service.ShoppingCartService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;@Component
public class ReservationExpirationTask {@Autowiredprivate ShoppingCartService shoppingCartService;@Scheduled(fixedRate = 60000) // 每分钟执行一次public void releaseExpiredReservations() {shoppingCartService.releaseExpiredReservations();}
}
7. 配置 MyBatis-Plus
application.properties 保持不变:
# 数据库连接配置
spring.datasource.url=jdbc:mysql://localhost:3306/your_database_name
spring.datasource.username=your_username
spring.datasource.password=your_password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver# MyBatis-Plus 配置
mybatis-plus.mapper-locations=classpath:mapper/*.xml