产品经理给到前端展示界面,得到要展示的view object之后,应该优先考虑领域模型,再考虑数据库表结构.(例如用户信息的的密码字段需要考虑分配在另一张表中,或者需要考虑领域模型中级联嵌套的关系)
此项目中,考虑到商品库存跟交易流水相关,将其分配在另一张表中,以便以后面的分库分表操作,性能优化和水平拆分.
销量字段在此项目中考虑只作用于展示,与商品信息放到一起.当用户发生交易行为后,通过异步的方式给销量值加1,而不会影响下单主链路
价格字段,在模型中采用BigDecimal类型,数据库中采用double类型.
1.面向领域设计:所有的创建完成后都必须返回创建完成的对象实体,因为必须要让上游知道创建完成后的对象的状态.
2.在插入没有主键,需要数据库使用默认值和自增方式产生主键的对象后,会自动给该对象赋值主键.
1.使用Java8的stream API统一转换list
//使用Java8的stream API
List<ItemModel> itemModelList = itemDOList.stream().map(itemDO -> {
ItemStockDO itemStockDO = itemStockDOMapper.selectByItemId(itemDO.getId());
ItemModel itemModel = this.convertModelFromDataObject(itemDO, itemStockDO);
return itemModel;
}).collect(Collectors.toList());
从url中取得元素的方法
function getParam(paramName) {
paramValue = "", isFound = !1;
if (this.location.search.indexOf("?") == 0 && this.location.search.indexOf("=") > 1) {
arrSource = unescape(this.location.search).substring(1, this.location.search.length).split("&"), i = 0;
while (i < arrSource.length && !isFound)
arrSource[i].indexOf("=") > 0 && arrSource[i].split("=")[0].toLowerCase() == paramName.toLowerCase() && (paramValue = arrSource[i].split("=")[1], isFound = !0), i++
}
return paramValue == "" && (paramValue = null), paramValue
}
1.落单减库存优于支付减库存
落单减库存:在创建订单之前将指定数量的库存锁定给该用户使用,能够保证用户支付后买到商品.
支付减库存:在创建订单之前只是确保有指定数量的库存,而不锁,等到支付过后才将库存扣减.无法保证用户不会超买商家的商品,需要走退款流程.只在商家未来保证用户交易率且有额外库存的情况下使用(避免用户恶意下单而不支付).
2.独立出来的商品库存表在某些高压的情况下可以做降级,专门独立出一个库存服务对其做减操作.而且减库存时,将会对商品库存表上行锁,分开设计表则不会影响商品表的查询性能.
3.数据库update时可将其返回值设为int,表示影响行数,从而判断逻辑上是否正确执行.(不需要update完再select,优化了查询)
(1) java8中有LocalDateTime,是吸收了joda-time方式并做了一套实现,依旧更建议使用jada-time.
<dependency>
<groupId>joda-timegroupId>
<artifactId>joda-timeartifactId>
<version>2.9.1version>
dependency>
(2) select … for update 是为了在查询时,避免其他用户以该表进行插入,修改或删除等操作,造成表的不一致性.select查询是不加锁的,select…for update是会加锁的,而且是悲观锁.
在where 后面查询条件是主键索引,唯一索引时候是行锁
查询条件是普通字段时候加的是表锁
(3) mysql不像oracle可以获得一个自增序列,可以使用一张表来维护.
@Transactional(propagation = Propagation.REQUIRES_NEW)
//不管该方法是否在事务中,都会开启一个新的事务,不管外部事务是否成功
//最终都会提交掉该事务,为了保证订单号的唯一性,防止下单失败后订单号的回滚
//下单是被也不应该再次使用失败的订单号
private String generateOrderNo() {
//订单有16位
StringBuilder stringBuilder = new StringBuilder();
//前8位为时间信息,年月日
LocalDateTime now = LocalDateTime.now();
String nowDate = now.format(DateTimeFormatter.ISO_DATE).replace("-", "");
stringBuilder.append(nowDate);
//中间6位为自增序列
//获取当前sequence
int sequence = 0;
SequenceDO sequenceDO = sequenceDOMapper.getSequenceByName("order_info");
sequence = sequenceDO.getCurrentValue();
sequenceDO.setCurrentValue(sequenceDO.getCurrentValue() + sequenceDO.getStep());
sequenceDOMapper.updateByPrimaryKeySelective(sequenceDO);
//拼接
String sequenceStr = String.valueOf(sequence);
for (int i = 0; i < 6 - sequenceStr.length(); i++) {
stringBuilder.append(0);
}
stringBuilder.append(sequenceStr);
//最后两位为分库分表位,暂时不考虑
stringBuilder.append("00");
return stringBuilder.toString();
}
public interface OrderService {
//1.通过url上传过来秒杀活动id,然后下单接口内校验对应id是否属于对应商品且活动已开始
//2.直接在下单接口内判断对应的商品是否存在秒杀活动,若存在进行中的则以秒杀价格下单
//倾向于使用第一种形式,因为对同一个商品可能存在不同的秒杀活动,而且第二种方案普通销售的商品也需要校验秒杀
OrderModel createOrder(Integer userId, Integer itemId, Integer promoId, Integer amount) throws BusinessException;
}