我有个action方法是取消订单用的在取消之前先判断订单状态是不是已取消不是的话就改成已取消并且把订单的钱加到账户但是测试的时候  连续快速的点两次取消钱加了两次  有可能是什么原因引起的是两次请求同时执行了?
action
先查询订单状态  判断是否已取消
然后
boolean res = this.ordersManager.cancelOrder(uporders);
dao层public boolean cancelOrder(Orders orders) throws DataAccessException {
Session session =null;
Transaction tx = null;
Balance bal = null;
try{
session = hibernateTemplate.getSessionFactory().openSession();
tx = session.beginTransaction();



ClientLoginInfo clientLoginInfo = orders.getClientLoginInfo();
String manualName=orders.getClientLoginInfo().getClientUserName();
double money=orders.getSumMoney();

Company c = orders.getClientLoginInfo().getCompany();
Integer companyId = c.getCompanyId();
List list = this.hibernateTemplate.find(
"from Balance as g where g.company.companyId=?", companyId);
if (list.isEmpty()) {
bal = new Balance();
bal.setCompany(c);
bal.setBalanceMoney(money);
session.save(bal);
} else {
 bal= (Balance) list.get(0);
bal.setBalanceMoney(bal.getBalanceMoney()
+ money);
session.merge(bal);
}
                           session.merge(orders);

tx.commit();
return true;
}catch (DataAccessException e) {
tx.rollback();
e.printStackTrace();
throw e;
}finally{
if(session!=null)
session.close();
}
}

解决方案 »

  1.   

    hibernate没有用过,真不熟悉。我不知道LZ操作的是数据库还是内存。具体是mysql还是oracle还是sqlserver。连续点击两次那就说明连续发了两个请求,可能这两个请求是不同线程来完成的。所以还是要加锁。在数据库上加锁也成,在内存里面加锁也行。
      

  2.   

    操作的是数据库
    sqlserver的
      

  3.   

     连续点了两次取消,这应该是属于重复提交的问题吧."取消"的按钮点了两次,这个按钮触发的也是提交表单的操作,相当于提交了两次表单.Struts里面是有处理重复提交的机制的,需要用到"令牌"机制.
      

  4.   

    cancelOrder(Orders orders)多个线程同时操作orders,必然会引发线程安全问题。
    这么弄:
    cancelOrder(Orders orders){
        synchronized(orders){
            ********
       }
    }
      

  5.   

    你需要确认一下了.你点两次提交是否确认进入了两次action.
    如果进入了,那就是重复提交了.重复提交就需要用到令牌来验证.来避免重复提交.
      

  6.   

    五楼的并发说法是不正确的,点了两次操作同时进行.这种说法不成立,cancelOrder(Orders orders)多个线程同时操作orders,必然会引发线程安全问题。
    这么弄:
    cancelOrder(Orders orders){
      synchronized(orders){
      ********
      }
    }
    这里面的orders是个参数,并不是全局的变量,不存在多个线程去同时操作的情况.每次操作传进来的orders必然是一个新的orders.除非是两次操作的orders是同样的值,那这就是表单重复提交的本质了.
      

  7.   

    快速点击两次属于重复提交的问题,可以看看应对重复提交时的一些保证机制,比如struts的令牌。或者你可以改下你的业务逻辑,如同 5楼 的方法给cancelOrder方法加锁。
      

  8.   

    如果说 dao是多实例的话   就是每个请求都是不同的dao对象   那么用synchronized起作用吗  求详解
      

  9.   

    重复提交的问题最好的解决方案,是用filter来实现.对所有的请求进行一次拦截.然后检查请求的令牌.
      

  10.   

    链接也可以disable吧,(我不知道刷新后有没有办法保持disable的状态)
    当然不喜欢这种方式可以在底层限制,如果不是多线程导致的,同步就没法阻止,这些都要去确认的
    关键先找到原因吧
      

  11.   

    肯定不是重复提交的问题,涉及数据锁的问题了
    重现也没有什么问题,用账户A登录,人为在其查询出状态后设置断点不操作,用账户B登录进行同样操作但这次不断点,肯定会执行两次了,简单一点的做法借助于数据库的锁机制,hibernate使用悲观锁在查询是就锁定这条记录,其他的查询会等待,直到加钱的逻辑commit,query.setLockMode("user",LockMode.UPGRADE); // 加锁 
      

  12.   

    我是想表达线程A在查询订单状态的select语句加锁此时状态是"未返还",其它的线程B查询此条数据的进程会等待,待我改变订单状态为"已返还",线程B查询继续运行查询的结果就是"已返还"
    如果不加锁就会出现这样的情况,我说的数据锁是其中的解决方法之一,完全可以把查询和返还放在一个方法用synchronized 关键字达到同样的效果
      

  13.   

    考虑到性能问题,我首先想到的利用select order from order where order.id=? for update 来对一条订单数据加锁,第二中方案性能相比差了点,这是业务特点决定的,得加锁,这是个人见解,如果你有更好的方案希望提出来大家共同学习下
      

  14.   

    ".如果查询还需要等待修改的完成,这不现实的.比如一万个用户在操作,同时在修改...."这是楼主的问题所在,根据业务需要得加锁,同时得考虑使加锁的数量尽可能的少....
    我估计你liupengxia经常用的是oracle数据库,oracle数据库数据就我了解的是一般不会出现读锁定,但其他数据库就是读锁定,必须得等update执行提交完了才可以执行select
      

  15.   

    for update这样的语句在实际业务应用中是不考虑使用的.这对程序的性能影响非常大.而且还是这样的订单数据。楼主说的这个问题可以假想一下: 有两个方法,一个方法是查询订单状态的。另一个方法是修改订单的。 先查询状态,如果状态是对的,就调用修改订单的操作。这种情况下,在查询订单的时候,修改操作肯定是还没有进行的。这个时候如果两个线程进入了查询状态的判断,这个判断都是成立的。于是,两个线程就又都进行了订单的修改操作。
     
     上面我作的假想,应该就是楼主提的问题产生的原因了。  在这种情况下,如果在查询的时候加锁。这不可能实现的。
      

  16.   

     
    用了你的方法    发现确实是可以同时进入两个的
    第一次还没改的时候 第二个已经进来了
    goldenfish1919 说的 加锁很好用  不过用错了地方   应该加在action中
    加在dao的话  第二次会卡在dao开始的部分   等第一次完了  他还会执行
    我的判断在action里
      

  17.   

    查询方法和修改方法在一个事务里调用就可以了,这是我个人认为的"service"层可以存在的理由,其实我很想知道下liupengxia你会怎么处理这种情况?不使用数据库for update 或者 java 的 synchronized 
      

  18.   

     最简易的方案就是在修改的时候进行操作。SQL如下。
    update orders set money=.....,status =1 where status<>1.这下前面一个提交操作把状态改了,第二个提交操作就不能再改变状态了。
      

  19.   

    用加锁,这种想法首先就走了岐路。并没有从根本上解决问题。问题的根本就是有了两次提交。这两次提交就是重复提交了。有经验的技术人员对这种情况都不会用加锁这种严重影响性能的方案的。这样的程序的可用率太低了。最好的方案还是对请求进行拦截,如果是同样的请求,直接被拦截。根本就不会进入业务处理的action。其次就是在数据库层面对状态进行严格的控制。修改的时候判断它的判断是否是老的状态,如果是老的状态就把状态改成新的。这样第二次进入修改,以状态作为修改条件就可以了。
      

  20.   

    money不在orders表没关系。只需要保证money所在的表有状态就行。
      

  21.   

    这个思路稍微有点问题,如果我是返200块,我第一次返20块,第二次反180块,逻辑好像有点问题了,其实你这个思路最好的实现就是用hibernate的乐观锁,select出的数据带版本信息,更新是 where语句加上版本信息,版本不一致时更新数据为零
      

  22.   


    Service层对事务的控制,只是保证数据的一致性,如果操作失败了还能回滚。这跟此问题没有必然联系。
      

  23.   

    最后一个问题如果我在spring里把action定义为prototype会怎么样是不是 synchronized会失效?
      

  24.   

    失效的,但你可以针对class同步public static void main(String[] args) {
    synchronized (Test.class) {
    //you code here
    }
    }
      

  25.   


    那要看你的action是多例还是单例。如果是单例,加锁程序就不能用了。只能单线程来用。
    如果是多例,那加锁与不加锁是一样的。因为你加的锁是对象锁,锁住的是单前的线程对象,但是另一个提交的请求还是会进来。
      

  26.   


    错了,首先要在表单提交前保存一个Token,所以在请求一到来的事件里要有 saveToken(request,true)语句; 这时你可以在表单提交的页面的源码中看到一个隐藏域, 如:〈input type="hidden" name="org.apache.struts.taglib.html.TOKEN" value="6aa35341f25184fd996c4c918255c3ae"〉提交上来后,到了insert事件里面了,这里要判断请求中的Token 和保存在会话中的Token是否一样,如果一样,则表明是第一次提交.如果不一样,则表明是"重复提交".
      

  27.   

    打个比方说下WEB情况下很有可能出现的场景,同一个帐号在多台机器上登录,操作同一订单........
    我猜楼主的解决方法就是,最不完善的方法了,我造孽了....阿弥陀佛
    spring 设置成 prototype
    action 执行方法为 public String order(){
    synchronized (OrderAction.class) {
    // update order 
    }
    }
      

  28.   

    我今天刚开通CSDN,大伙可以交个朋友.我的QQ :68100893.如果有技术问题,可以跟我联系.大家可以一起交流.
      

  29.   

    顺便提醒一下楼主.放在action去做锁控制.这种做法我前所未见哦.呵呵.
    这种做法很容易就会有性能瓶颈。而且还没有从根本上解决问题。