我正在尝试使用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中,但该网站不会被发布,这只是为了考试,教授告诉我们不要这样做'不管理安全性(他只关心软件工程)。
如果您发现我做错了什么,请告诉我,提前谢谢您。
即使版本不是预期的版本,您的更新语句也将正确执行。您需要检查语句是否正确执行:
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");
}
}