1.初始方案
(1)表设计
商品表设计:热销商品提供给用户秒杀,有初始库存。
import java.io.Serializable;
/**
* t_seckillgoods
* @author
*/
public class TSeckillgoods implements Serializable {
/**
* id
*/
private Integer id;
/**
* 商品库存
*/
private Integer remainnum;
/**
* 商品名
*/
private String goodsname;
private static final long serialVersionUID = 1L;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getRemainnum() {
return remainnum;
}
public void setRemainnum(Integer remainnum) {
this.remainnum = remainnum;
}
public String getGoodsname() {
return goodsname;
}
public void setGoodsname(String goodsname) {
this.goodsname = goodsname;
}
}
秒杀订单表设计:记录秒杀成功的订单情况:
import java.io.Serializable;
/**
* t_seckillOrder
* @author
*/
public class TSeckillorder implements Serializable {
private Integer id;
private String consumer;
private Integer goodsid;
private Integer num;
private static final long serialVersionUID = 1L;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getConsumer() {
return consumer;
}
public void setConsumer(String consumer) {
this.consumer = consumer;
}
public Integer getGoodsid() {
return goodsid;
}
public void setGoodsid(Integer goodsid) {
this.goodsid = goodsid;
}
public Integer getNum() {
return num;
}
public void setNum(Integer num) {
this.num = num;
}
}
(2)Dao设计:
商品dao,库存减去购买数量
public interface TSeckillgoodsDao {
@Update("UPDATE t_seckillgoods g set g.remainnum=g.remainnum - #{num} where g.id=1")
@Transactional
int reduceStock(Integer num);
}
订单dao,增加订单信息,原始mapper.
import com.javasvip.model.vo.TSeckillorder;
public interface TSeckillorderDao {
int deleteByPrimaryKey(Integer id);
int insert(TSeckillorder record);
int insertSelective(TSeckillorder record);
TSeckillorder selectByPrimaryKey(Integer id);
int updateByPrimaryKeySelective(TSeckillorder record);
int updateByPrimaryKey(TSeckillorder record);
}
(3)Controller层的设计:
import com.javasvip.dao.TSeckillgoodsDao;
import com.javasvip.model.vo.TSeckillgoods;
import com.javasvip.service.impl.SecKillServerImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class SecKillController {
@Autowired
private TSeckillgoodsDao tSeckillgoodsDao;
@Autowired
private SecKillServerImpl secKillServer;
@RequestMapping("/seckill.html")
@ResponseBody
public String seckill(String consumer,int goodsId,Integer num) throws Exception{
TSeckillgoods tSeckillgoods=tSeckillgoodsDao.selectByPrimaryKey(goodsId);
if (tSeckillgoods.getRemainnum()>num){
Thread.sleep(1000);
//减去库存
tSeckillgoodsDao.reduceStock(num);
//保存订单
secKillServer.saveOrder(consumer,goodsId,num);
return "购买成功";
}
return "购买失败,库存不足";
}
}
(4)server设计:
import com.javasvip.dao.TSeckillgoodsDao;
import com.javasvip.dao.TSeckillorderDao;
import com.javasvip.model.vo.TSeckillorder;
import com.javasvip.service.ISecKillServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class SecKillServerImpl implements ISecKillServer {
@Autowired
private TSeckillorderDao tSeckillorderDao;
@Autowired
private TSeckillgoodsDao tSeckillgoodsDao;
@Override
public void saveOrder(String consumer, int goodsId, Integer num) {
TSeckillorder tSeckillorder=new TSeckillorder();
tSeckillorder.setConsumer(consumer);
tSeckillorder.setGoodsid(goodsId);
tSeckillorder.setNum(num);
tSeckillorderDao.insert(tSeckillorder);
}
}
(5)测试方法,模拟高并发下,很多人来购买同一个热门商品的情况:(url注意路径和端口号)
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.List;
@Controller
public class TestController {
private static final Logger logger = LoggerFactory.getLogger(TestController.class);
final String url="http://127.0.0.1/seckill.html";
@RequestMapping("/sumibtmitOrder")
@ResponseBody
public String sumibtmitOrder(){
final SimpleClientHttpRequestFactory httpRequestFactory=new SimpleClientHttpRequestFactory();
for(int i=0;i<50;i++){
final String consumerName="consumer"+i;
new Thread(new Runnable() {
@Override
public void run() {
ClientHttpRequest request=null;
try{
URI uri=new URI(url+"?consumer=consumer"+consumerName+"&goodsId=1&num=1");
request=httpRequestFactory.createRequest(uri,HttpMethod.POST);
InputStream body=request.execute().getBody();
BufferedReader br=new BufferedReader(new InputStreamReader(body));
String line="";
String result="";
while((line=br.readLine())!=null){
result+=line;
}
System.out.println(consumerName+":"+result);
}catch (Exception e){
e.printStackTrace();
}
}
}).start();
}
return "sumibtmitOrder";
}
}
访问localhost:port/sumibtmitOrder,就可以测试了
预期情况:因为我们只对秒杀商品(123456)初始化了10件,理想情况当然是库存减少到0,订单表也只有10条记录。
(6)测试结果:
商品表:
订单表:
(7)原因分析:
因为多个请求访问,仅仅是使用dao查询了一次数据库有没有库存,但是比较恶劣的情况是很多人都查到了有库存,这个时候因为程序处理的延迟,没有及时的减少库存,那就出现了脏读。如何在设计上避免呢?最笨的方法是对SecKillController的seckill方法做同步,每次只有一个人能下单。但是太影响性能了,下单变成了同步操作。
来源:www.cnkirito.moe ,自己又操作了一般,明白了一些东西,实例最能让人去理解了。