为什么我的乐观锁实现不能处理两个竞争请求?

问题描述 投票:0回答:1

我正在尝试使用java、MySQL数据库和JDBC实现一个电子商务网站。

我在使用乐观锁管理并发时遇到一些问题。事实上,如果用户“A”尝试购买 3 件产品,而用户“B”尝试同时购买 2 件同一产品(但该产品的总可用件数为 4),我将无法预测结果。 事实上,我尝试了一下,还剩下 2 个可用项目,该列的版本正确地增加了 1,但两个用户都付款并完成了订单(两笔交易之一没有回滚)。

似乎用户 B 的请求覆盖了用户 A 的请求,因为他们都在其中之一增加版本之前检查了版本。因此,当它们到达提交点时,最后一个将覆盖另一个的结果。

当然我做错了一些事情,也许我什至误解了乐观锁的概念,你能帮助我吗?

这是控制器:

 String applicationMessage = null;
        DAOFactory sessionDAOFactory = null;
        DAOFactory daoFactory = null;
        User loggedUser;


        Logger logger = LogService.getApplicationLogger();

        try {

            Map sessionFactoryParameters = new HashMap<String, Object>();
            sessionFactoryParameters.put("request", request);
            sessionFactoryParameters.put("response", response);
            sessionDAOFactory = DAOFactory.getDAOFactory(Configuration.COOKIE_IMPL, sessionFactoryParameters);
            sessionDAOFactory.beginTransaction();

            UserDAO sessionUserDAO = sessionDAOFactory.getUserDAO();
            loggedUser = sessionUserDAO.findLoggedUser();

            daoFactory = DAOFactory.getDAOFactory(Configuration.DAO_IMPL, null);
            daoFactory.beginTransaction();

            Double CardNumber = Double.parseDouble(request.getParameter("CardNumber"));
            int CVV = Integer.parseInt(request.getParameter("cvv"));
            String Name = request.getParameter("name");
            String ExpireDate = request.getParameter("expireDate");



            /* I find the pending order linked with logged user*/
            OrderDAO orderDAO = daoFactory.getOrderDAO();
            Order order = orderDAO.findByLoggedUserAndPendingStatus(loggedUser);

            //I find the cart
            CartDAO cartDAO = daoFactory.getCartDAO();
            Cart cart = cartDAO.findCartByOrder(order);

            //Take all the cart item inside the cart
            CartItemDAO cartItemDAO = daoFactory.getCartItemDAO();
            List<CartItem> cartItems = cartItemDAO.getCartItems(cart);



            //I create the payment
            PaymentDAO paymentDAO = daoFactory.getPaymentDAO();
            paymentDAO.create(cart.getPrice_cart(), order, CardNumber, CVV, Name, Expire);

            //Set complete flag to the order
            orderDAO.setCompleteStatus(order);

            //Decrease the quantity of the product
            for (CartItem cartItem : cartItems) {
                ProductDAO productDAO = daoFactory.getProductDAO();

                productDAO.checkAndDecreaseQuantity(cartItem.getProduct().getId_product(), cartItem.getQuantity());

            }

            daoFactory.commitTransaction();
            sessionDAOFactory.commitTransaction();

            applicationMessage = "Payment received, order executed successfully";

            request.setAttribute("loggedOn", loggedUser != null);
            request.setAttribute("loggedUser", loggedUser);
            request.setAttribute("applicationMessage", applicationMessage);
            request.setAttribute("viewUrl", "jsp/homeManagement/Home");

        } catch (OptimisticLockException e) {
            logger.log(Level.SEVERE, "Controller Error", e);
            try {
                if (daoFactory != null) daoFactory.rollbackTransaction();
                if (sessionDAOFactory != null) sessionDAOFactory.rollbackTransaction();

            } catch (Throwable t) {
            }
            throw new RuntimeException(e);

        } catch (NotAvailableProductException e) {
            logger.log(Level.INFO, "Controller Error", e);
            try {
                if (daoFactory != null) daoFactory.rollbackTransaction();
                if (sessionDAOFactory != null) sessionDAOFactory.rollbackTransaction();
            } catch (Throwable t) {
            }
            throw new RuntimeException(e);


        } catch (Exception e) {
            logger.log(Level.SEVERE, "Controller Error", e);
            try {
                if (daoFactory != null) daoFactory.rollbackTransaction();
                if (sessionDAOFactory != null) sessionDAOFactory.rollbackTransaction();

            } catch (Throwable t) {
            }
            throw new RuntimeException(e);


        } finally {
            try {
                if (daoFactory != null) daoFactory.closeTransaction();
                if (sessionDAOFactory != null) sessionDAOFactory.closeTransaction();
            } catch (Throwable t) {
            }
        }

productDAO.checkAndDecreaseQuantity是唯一实现乐观锁的方法,可以抛出NotAvailableProductException和OptimisticLockException:

public int checkAndDecreaseQuantity(Long ID_product, int quantity) throws NotAvaibleProductException, OptimisticLockException {
    PreparedStatement ps;
    Product product=null;

    try {

      String sql
              = " SELECT * FROM PRODUCT "
              + " WHERE "
              + "   Deleted=0 AND Blocked=0 AND ID_product=? AND AvailableQuantity>=?";


      ps = conn.prepareStatement(sql);
      ps.setLong(1, ID_product);
      ps.setInt(2,quantity);


      ResultSet resultSet = ps.executeQuery();

      boolean exist=false;
      while (resultSet.next()) {
        product = read(resultSet);
        exist=true;
      }

      resultSet.close();

      if (!exist) {
        throw new NotAvaibleProductException("ProductDAOJDBCImpl.decreaseQuantity: The desired quantity of the selected product is less than the available quantity, or the product has been deleted or blocked.");
      }


      sql = " UPDATE Product "
              + " SET AvailableQuantity=?, version=? "
              + " WHERE "
              + " ID_product=? AND Blocked=0 AND Deleted=0 AND version=?";

      ps = conn.prepareStatement(sql);
      int i=1;
      ps.setInt(i++, product.getQuantity()-quantity);
      ps.setLong(i++, product.getVersion()+1);
      ps.setLong(i++, ID_product);
      ps.setLong(i++, product.getVersion());

      ps.executeUpdate();
      ps.close();


    } catch (SQLException e) {
      String errorMessage = e.getMessage();
      int errorCode = e.getErrorCode();

      if (errorMessage.contains("Unknown column") || errorCode == 1054) {
        throw new OptimisticLockException("Exception lock optimistic quantity update");
      } else {
        throw new RuntimeException(e);
      }
    }
    return product.getQuantity()-quantity;
  }

我知道存在一些安全问题,例如将信用卡信息存储到数据库中并将UserID直接存储在cookie中,但该网站不会被发布,这只是为了考试,教授告诉我们不要这样做'不管理安全性(他只关心软件工程)。

如果您发现我做错了什么,请告诉我,提前谢谢您。

java mysql jdbc optimistic-locking
1个回答
0
投票

即使版本不是预期的版本,您的更新语句也将正确执行。您需要检查语句是否正确执行:

  sql = " UPDATE Product "
          + " SET AvailableQuantity=?, version=? "
          + " WHERE "
          + " ID_product=? AND Blocked=0 AND Deleted=0 AND version=?";

  try (PreparedStatement ps = conn.prepareStatement(sql)) {
      int i=1;
      ps.setInt(i++, product.getQuantity()-quantity);
      ps.setLong(i++, product.getVersion()+1);
      ps.setLong(i++, ID_product);
      ps.setLong(i++, product.getVersion());

      if (ps.executeUpdate() == 0) {
          throw new OptimisticLockException("Exception lock optimistic quantity update");
      }
  }
© www.soinside.com 2019 - 2024. All rights reserved.