原文:http://blog.csdn.net/zhouyong0/article/details/7933631@H_404_3@
插入数据库,在大家开发过程中是很经常的事情,假设我们有这么一个需求:@H_404_3@
1、我们需要接收一个外部的订单,而这个订单号是不允许重复的@H_404_3@
3、外部经常插入相同的订单,对于已经存在的订单则拒绝处理@H_404_3@
packagecom.yhj.test;@H_404_3@
@H_404_3@
importcom.yhj.dao.OrderDao;@H_404_3@
importcom.yhj.pojo.Order;@H_404_3@
/**@H_404_3@
*@Description:并发测试用例@H_404_3@
@AuthorYHJcreate at 2011-77上午08:41:44@H_404_3@
@FileNamecom.yhj.test.TestCase.java@H_404_3@
*/@H_404_3@
publicclassTestCase {@H_404_3@
* data access object class for deal order@H_404_3@
privateOrderDaoorderDao;@H_404_3@
插入测试@H_404_3@
@paramobject要插入的实例@H_404_3@
@authorYHJ create at 201108:43:15@H_404_3@
@throwsException@H_404_3@
voiddoTestForInsert(Order order)throwsException {@H_404_3@
Order orderInDB =orderDao.findByName(order.getOrderNo());@H_404_3@
if(null!= orderInDB)@H_404_3@
thrownewException("the order has been exist!");@H_404_3@
orderDao.save(order);@H_404_3@
}@H_404_3@
@H_404_3@
}@H_404_3@
这样很显然,在单线程下是没问题的,但是多线程情况下就会出现一个问题,线程1先去访问DB,查找没有,开始插入,这时候线程2又来查找DB,而此时线程1插入的事务还没有提交,线程2没有查到该数据,也进行插入,于是,问题出现了,插入了2条一样订单。@H_404_3@
对于这种情况,好像如果不用数据库做唯一性约束又不借助外部其他的一些工具,是没有办法实现的。那怎么做呢?@H_404_3@
importcom.yhj.util.MemcacheUtil;@H_404_3@
importcom.yhj.util.MemcacheUtil.UNIT;@H_404_3@
orderDao;@H_404_3@
voiddoTestForInsert(Order order){@H_404_3@
String key=null;@H_404_3@
try{@H_404_3@
orderDao.findByName(order.getOrderNo());@H_404_3@
key=order.getOrderNo();@H_404_3@
插缓存,原子性操作,插入失败表明已经存在@H_404_3@
if(!MemcacheUtil.add(key,order,MemcacheUtil.getExpiry(UNIT.MINUTE,1)))@H_404_3@
插DB@H_404_3@
orderDao.save(order);@H_404_3@
}catch(Exception e) {@H_404_3@
e.printStackTrace();@H_404_3@
finally{@H_404_3@
MemcacheUtil.del(key);@H_404_3@
@H_404_3@
1、查找数据库,如果数据库已经存在则抛出异常@H_404_3@
2、插入缓存,如果插入失败则表明缓存中已经存在,抛出异常@H_404_3@
3、如果上述2步都没有抛出异常,则执行插入数据库的操作@H_404_3@
在并发的情况下,线程1先查找数据库,发现没有,继续执行,写缓存,这时候线程2开始查找数据库,发现没有,则写缓存,结果缓存中已经存在,写缓存失败,抛出异常,返回已存在。线程1执行插入数据库成功,删除缓存。以后再来的线程发现数据库已经存在了,则不在向下执行,直接返回.。@H_404_3@
机器异常情况下,不能执行finally语句,但是放在memcache中的数据会在1分钟后超时。@H_404_3@
}@H_404_3@
}@H_404_3@
我们所预料的是2个线程同时操作,假设有更多的并发线程呢?@H_404_3@
时刻1:@H_404_3@
时刻2@H_404_3@
线程1写缓存@H_404_3@
时刻3@H_404_3@
线程2开始写缓存@H_404_3@
时刻4@H_404_3@
线程2写缓存失败,抛出异常,执行finally@H_404_3@
线程3开始写缓存@H_404_3@
时刻5@H_404_3@
线程2执行finally,删除缓存,开始构建返回结果@H_404_3@
线程3发现缓存不存在(被线程2删除),写缓存@H_404_3@
时刻6@H_404_3@
线程1成功返回@H_404_3@
线程2成功返回@H_404_3@
时刻7@H_404_3@
因此上述代码仍然有插入多条重复记录的可能,我们在并发20的测试中发现成功插入了5笔订单,其中4笔是不应该插入的!@H_404_3@
}//finally{@H_404_3@
//MemcacheUtil.del(key);@H_404_3@
//}@H_404_3@
这样是保证了只有插入DB成功了才会删除缓存,但是当插入DB的时候发生了一个异常,删除缓存就不会再执行,虽然我们有一分钟超时,但意味着我们一分钟内该笔订单是不能再被处理的,而实际上这边订单并没有处理成功,所以这样是不满足需求的!@H_404_3@
继续改进@H_404_3@
booleanneedDel=false;@H_404_3@
if(!MemcacheUtil.add(key,MemcacheUtil.getExpiry(UNIT. needDel=true;@H_404_3@
if MemcacheUtil.del(key);@H_404_3@
在其他异常执行的时候是不会删除缓存的,我们套在之前的代码上,线程2判断缓存中存在抛出异常执行finally的时候是不会删除缓存的,因此线程3没有机会执行写缓存的操作,从而保证了线程1是唯一能够插入DB的。@H_404_3@
还有没有其他漏洞呢?期待大家发现……@H_404_3@