一、效果/要求/目的
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=8090
server.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=false
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.tomcat.max-active=1000
spring.datasource.tomcat.max-idle=2000
spring.datasource.tomcat.initialSize=1000
# Hibernate ddl auto (create, create-drop, update)
spring.jpa.properties.hibernate.hbm2ddl.auto=update
spring.jpa.properties.hibernate.show_sql=true
########################################################
### Java Persistence Api 自动进行建表
########################################################
# Specify the DBMS
spring.jpa.database = MYSQL
# Show or not log for each sqlquery
spring.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.MySQL5Dialect
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL55Dialect
spring.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;//商品库存数
}
复制代码
行级锁的实现过程及相关原理,供参考以后项目可以参考使用。
转载声明:本文为博主原创文章,未经博主允许不得转载
⚠️注意 ~
?本期内容就结束了,如果内容有误,麻烦大家评论区指出!
如有疑问❓可以在评论区或私信,尽我最大能力帮大家解!
如果我的文章有帮助到您,欢迎点赞+关注✔️鼓励博主,您的鼓励是我分享的动力
评论