写点什么

实战 | SpringBoot+MySQL 事务 / 行级锁实现商品减库存

  • 2022 年 6 月 13 日
  • 本文字数:4899 字

    阅读完需:约 16 分钟

实战 | SpringBoot+MySQL事务/行级锁实现商品减库存

一、效果/要求/目的

1.目的/解决

       实现电商项目中,由于多个用户某一时间段同时下单时,解决购买同一种商品情况下,扣减库存为负的问题。

2.要求

  创建数据库时,MySQL 的数据库要求:

 1 )MySQL 表引擎 Innodb                

2 )开启事务

项目在业务接口中类头出开启事务监控模式(@Transactional)

 3 )Jmeter


​            


4)Postman

 java 环境下安装工具:

3.效果


​二、基础原理

1)SpringBoot2.0 基础知识,jpa 使用,a.yml,配置,事务处理,异常回滚等

2)MySQL 锁:

          实验环境:mysql5.6

          存储引擎:InnoDB

          使用锁:行级锁

扩展】:

- MyISAM:它不支持事务,也不支持外键,尤其是访问速度快,对事务完整性没有要求或者以 SELECT、INSERT 为主的应用基本都可以使用这个引擎来创建表。

- InnoDB :InnoDB 是一个健壮的事务型存储引擎,这种存储引擎已经被很多互联网公司使用,为用户操作非常大的数据存储提供了一个强大的解决方案。InnoDB 就是作为默认的存储引擎。InnoDB 还引入了行级锁定和外键约束,在以下场合下,使用 InnoDB 是最理想的选择:

1.更新密集的表。InnoDB 存储引擎特别适合处理多重并发的更新请求。

2.事务。InnoDB 存储引擎是支持事务的标准 MySQL 存储引擎。

3.自动灾难恢复。与其它存储引擎不同,InnoDB 表能够自动从灾难中恢复。

4.外键约束。MySQL 支持外键的存储引擎只有 InnoDB。

5.支持自动增加列 AUTO_INCREMENT 属性。

       一般来说,如果需要事务支持,并且有较高的并发读取频率,InnoDB 是不错的选择

- MEMORY:使用 MySQL Memory 存储引擎的出发点是速度。为得到最快的响应时间,采用的逻辑存储介质是系统内存。虽然在内存中存储表数据确实会提供很高的性能,但当 mysqld 守护进程崩溃时,所有的 Memory 数据都会丢失。获得速度的同时也带来了一些缺陷。它要求存储在 Memory 数据表里的数据使用的是长度不变的格式,这意味着不能使用 BLOB 和 TEXT 这样的长度可变的数据类型,VARCHAR 是一种长度可变的类型,但因为它在 MySQL 内部当做长度固定不变的 CHAR 类型,所以可以使用。

MySQL 锁:[ ​​【mysql 当前锁 查看】​​ -----参考]



行锁(Record Locks) 间隙锁(Gap Locks) 临键锁(Next-key Locks)共享锁/排他锁(Shared and Exclusive Locks)

意向共享锁/意向排他锁(Intention Shared and Exclusive Locks)

插入意向锁(Insert Intention Locks)自增锁(Auto-inc Locks)


三、实现方式/应用/环境配置

1、环境配置

2、创建项目

 1)配置文件


########################################################### tomcat  配置######################################################### 监听端口server.port=8090server.address=0.0.0.0# tomcat最大线程数,默认为200#server.tomcat.max-threads=800# tomcat的URI编码server.tomcat.uri-encoding=UTF-8###########################################################datasource 数据库 配置MySQL数据源########################################################## spring.datasource.url=jdbc:mysql://localhost:3306/vcoo_fresh_store?useOldAliasMetadataBehavior=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&useSSL=falsespring.datasource.username=rootspring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driverspring.datasource.tomcat.max-active=1000spring.datasource.tomcat.max-idle=2000spring.datasource.tomcat.initialSize=1000# Hibernate ddl auto (create, create-drop, update)spring.jpa.properties.hibernate.hbm2ddl.auto=updatespring.jpa.properties.hibernate.show_sql=true########################################################### Java Persistence Api 自动进行建表######################################################### Specify the DBMSspring.jpa.database = MYSQL# Show or not log for each sqlqueryspring.jpa.show-sql = true# hibernate ddl auto (create,create-drop, update)spring.jpa.hibernate.ddl-auto = update# stripped before adding them tothe entity manager)#spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialectspring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL55Dialectspring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
复制代码


【扩展】:

      数据库方言:MySQL5Dialect 、MySQL55Dialect 区别:

2)代码实现

@CrossOrigin@RestController@RequestMapping("/CustomerOrder")public class CustomerOrderController {
@Autowired CustomerOrderService orderService;
//添加AOP注解日志管理 ---------使用见博客:https://mp.csdn.net/console/editor/html/105488678 @SysLogAspectValue( describtion = "客户创建订单", logType = "2", type = "POST", url = "/SaveOrder", table = "super_wx_order", params = "body", method = "POST" ) @ApiOperation(value = "客户创建订单", notes = "/SaveOrder") @PostMapping(value = "/SaveOrder") public Object SaveOrder(@RequestBody Map<String, String> body) { return orderService.SaveWxOrder(wxMemberId, wxMemberCartList);
}


}
复制代码


@Transactional(propagation = Propagation.REQUIRED)public interface CustomerOrderService {    /**   * 创建新订单   *    **/  @Transactional(propagation = Propagation.REQUIRED)  Map<String, Object> SaveWxOrder(String wxMemberId, String wxMemberCartLis ) throws Exception;
}
复制代码



@Service("CustomerOrderService")public class CustomerOrderServiceImpl extends SysConfigModel implements CustomerOrderService {     @Autowired  WxOrderRepository wxOrderRepository;     @Override  @Transactional(rollbackFor={Exception.class, RuntimeException.class, Error.class})  public Map<String, Object> SaveWxOrder(String memberId, String memberCartList ) throws Exception {    Map<String, Object> resMap = new HashMap<>();         // 处理库存和销售量     int editNum = productRepository.ReduceInventory(AAAA.getProductId(), productNum);     if (editNum != 1) {        log.error("库存更新失败:ProductId : " + detail.getProductId() + "--" + detail.getProductName() + "--productNum : " + productNum);//TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();throw new Exception(detail.getProductName() + "  " + ErrorMessage.error_order_save_inventory_one);// 更新库存应该返回更新1条记录否则视为失败     }        WxOrder order = new WxOrder();      order.setOrderId(orderId);      order.setWx_memberId(memberId);//会员编号      order.setCreateTime(new Date());      this.wxOrderRepository.saveAndFlush(order);         return resMap;  }

}
复制代码

  ①)异常


②)jpa 中行级锁使用方法:

       行级锁,对于 mysql,InnoDB 预设的是 Row-level Lock,但是,需要明确的指定主键,才会执行行级锁,否则执行的为表锁。

       锁解释:[ ​​MySQL中select * for update锁表的问题​​]

public interface WxProductRepository  extends JpaRepository<WxProduct, Long>{   
@Modifying(clearAutomatically = true) @Query(value = "update super_wx_product_table set `wx_product_invent` = `wx_product_invent`-:productInvent, `wx_buy_num`=`wx_buy_num` + :productInvent where `wx_product_id` = :productId and `wx_product_invent` >= :productInvent", nativeQuery = true) int ReduceInventory(@Param("productId")String productId, @Param("productInvent")int productInvent);
}
复制代码

【扩展】:

  ①)疑问:


实现方式原因:在查询数据同时进行更改数据信息

 ②    @Modifying(clearAutomatically = true) 注解解释:


 底层:



3)商品实体层

@Entity@Table(name = "s_wx_product")@Component@org.hibernate.annotations.Table(comment="小程序商品信息表", appliesTo = "s_wx_product")public class WxProduct extends BaseModel implements java.io.Serializable {
@ApiModelProperty("商品编号") @Column(name="wx_product_id",columnDefinition = "varchar(255) comment '商品编号'") private String wxProductId;//商品编号 @ApiModelProperty("商品名称") @Column(name="wx_product_name",columnDefinition = "varchar(255) comment '商品名称'") private String wxProductName;//商品名称 @ApiModelProperty("商品规格") @Column(name="wx_product_palate",columnDefinition = "varchar(255) comment '商品规格'") private String wx_productPalate;//商品名称 @ApiModelProperty("销售价格") @Column(name="wx_product_price",columnDefinition = "decimal(18,2) DEFAULT '0.00' comment '销售价格'") private BigDecimal wx_productPrice;//销售价格 @ApiModelProperty("优惠价格") @Column(name="wx_product_cost",columnDefinition = "decimal(18,2) DEFAULT '0.00' comment '优惠价格'") private BigDecimal wx_productCost;//销售价格 @ApiModelProperty("商品购买次数") @Column(name="wx_buy_num",columnDefinition = "bigint(5) comment '商品购买次数'") private Integer wx_buyNum;//商品购买次数 @ApiModelProperty("商品评论") @Column(name="wx_goods_comment",columnDefinition = "varchar(255) comment '商品评论'") private String wx_goodsComment;//商品评论 @ApiModelProperty("商品原图") @Column(name="wx_original",columnDefinition = "varchar(255) comment '商品原图'") private String wx_original; //商品原图 @ApiModelProperty("是否上架(1:上架2:下架)") @Column(name="wx_market_enable",columnDefinition = "int(5) comment '是否上架(0:上架 0:下架)'") private String wx_marketEnable;//是否上架(1:上架2:下架)
@ApiModelProperty("商品类型(1:团购2:零售)") @Column(name="wx_product_type",columnDefinition = "int(5) comment '商品类型(1:团购2:零售)'") private Integer wx_productType;//订单类型(1:团购2:零售) @ApiModelProperty("商品详情-1") @Column(name="wx_intro",columnDefinition = "text comment '商品详情-1'") private String wx_intro;//商品详情 @ApiModelProperty("商品详情-简介图2") @Column(name="wx_intro_img2",columnDefinition = "text comment '商品详情-简介图2'") private String wx_introImg2;//商品详情
@ApiModelProperty("排序") @Column(name="wx_product_sort",columnDefinition = "int(5) comment '商品排序'") private String wxProductSort;//订单类型(1:团购2:零售) @ApiModelProperty("商品库存数") @Column(name="wx_product_invent",columnDefinition = "int comment '商品库存数'") private int wx_productInvent = 0;//商品库存数 }
复制代码

行级锁的实现过程及相关原理,供参考以后项目可以参考使用。



转载声明:本文为博主原创文章,未经博主允许不得转载

⚠️注意 ~

?本期内容就结束了,如果内容有误,麻烦大家评论区指出

如有疑问❓可以在评论区私信,尽我最大能力帮大家解!

如果我的文章有帮助,欢迎点赞+关注✔️鼓励博主,您的鼓励是我分享的动力

发布于: 刚刚阅读数: 3
用户头像

还未添加个人签名 2022.06.11 加入

Java开发工程师-用博客的方式分享代码

评论

发布
暂无评论
实战 | SpringBoot+MySQL事务/行级锁实现商品减库存_微服务_写程序的小王叔叔_InfoQ写作社区