Java 管理数据库中的产品计数

Java 管理数据库中的产品计数,java,database,multithreading,concurrency,thread-safety,Java,Database,Multithreading,Concurrency,Thread Safety,如果这个问题看起来很幼稚,请原谅,但我遇到了一个场景,我需要管理电子商务商店数据库中的产品数量 有一个产品类,它带有一个整数变量productCount,表示站点用户可以看到的数据库中可用产品的数量。现在这个类被几个线程访问,或者可以说是电子商务站点的几个用户。每个人都在向购物车添加或删除产品 正在使用的ORM框架是hibernate 示例代码 @Entity @Table class Product{ @Column private int productCount; pu

如果这个问题看起来很幼稚,请原谅,但我遇到了一个场景,我需要管理电子商务商店数据库中的产品数量

有一个产品类,它带有一个整数变量
productCount
,表示站点用户可以看到的数据库中可用产品的数量。现在这个类被几个线程访问,或者可以说是电子商务站点的几个用户。每个人都在向购物车添加或删除产品

正在使用的ORM框架是hibernate

示例代码

@Entity
@Table
class Product{
   @Column
   private int productCount;

   public void addProductToCart(){
     // decrements the product count by 1 & updates the database
   }

   public void removeTheProductFromTheCart(){
    // increments the product count by 1 & updates the database
   }
从代码中可以清楚地看到,我需要对数据库中的产品计数进行并发检查,以防止更新丢失

另外,如果有多个用户试图在数据库中只添加一个剩余产品,也可以这样做。应将产品添加到哪个用户的购物车

我对此做了一些研究

我找到的可能方法是

  • 为产品创建单例类。这将确保整个应用程序中只有一个产品实例可用

  • 同步
    addProductToCart
    removeTheProductFromTheCart
    方法。 一次只允许一个线程更新产品计数&更新数据库

  • 使用数据库并发控制为
    productCount
    应用一些db事务隔离级别、乐观/悲观锁定。我使用的是mysql,默认隔离级别是
    REPEATABLE\u READ


  • 处理这一问题的最佳方法是什么。使用数据库并发控制

    为什么?

    • 如果您的电子商务应用程序绝对是修改产品数量的唯一方法,那么1和2就可以了。这是一个很大的假设。在开展业务和维护库存的过程中,商店可能需要其他方式来更新产品数量,而电子商务应用程序可能不是理想的解决方案。另一方面,数据库通常更容易连接到不同的应用程序中,这些应用程序有助于商店的库存过程

    • 数据库产品通常有很多故障安全机制,因此如果出现问题,您可以跟踪哪些事务成功,哪些事务没有成功,并且可以回滚到特定的时间点。在内存中浮动的java程序没有这种现成的功能,如果使用1或2,则必须自己开发。Spring和Hibernate以及其他类似的东西当然比什么都没有好,但是比较一下它们提供的东西和数据库提供的从一些电子灾难中恢复的东西


    我认为传统的分层方法在这方面会有所帮助-不确定这会有多大的改变,因为我不知道应用程序的大小/成熟度,但无论如何都会继续描述它,您可以选择哪些位是可行的

    理论…

    服务也称为“业务逻辑”、“业务规则”、“域逻辑”等。
    ^
    DAOs a.k.a.“数据访问对象”、“数据访问层”、“存储库”等。
    ^
    实体a.k.a.“模型”-数据库结构的ORM表示
    ^
    数据库
    
    将实体与DAO层分开是很有用的,这样它们只是简单的存储单元,您可以填充、比较等,而不包括作用于它们的方法。因此,这些只是数据库中内容的类表示,理想情况下不应该被定义如何使用它们的代码所污染

    DAO层提供了基本的CRUD操作,允许持久化、检索、合并和删除这些实体,而无需知道执行这些操作的上下文。在这里,单例可以有效地防止多次创建多个实例,但使用单例并不意味着线程安全。就我个人而言,我建议使用Spring来实现这一点(Springbeans默认为单例),但如果愿意的话,我想可以手动实现

    服务层是实现“域逻辑”的地方,即应用程序执行特定功能所需的特定操作组合。线程安全问题可以在这里解决,有时需要,有时不需要

    实际上…

    按照这种方法,您可能会得到类似的结果(为了简洁起见,省略了很多):

    @实体
    @桌子
    公共类产品{
    @许多酮
    @连接柱
    私人购物车;
    }
    @实体
    @桌子
    公共类购物车{
    @奥内托内
    @连接柱
    私人用户;
    @OneToMany(mappedBy=“shoppingCart”)
    私人套装产品;
    }
    公共类ShoppingCartDao{/*persist、merge、remove、findById等./}
    @交易的
    公共类ProductService(){
    私有ConcurrentMap锁=
    新的ConcurrentHashMap();
    public void addProductToCart(最终int产品ID、最终int用户ID){
    ShoppingCart ShoppingCart=shoppingCartDao.findByUserId(userId);
    Product Product=productDao.findById(productId);
    已同步(getCacheSyncObject(productId)){
    if(product.shoppingCart==null){
    产品.购物车(购物车);
    }否则{
    抛出新的CustomException(“产品已保留”);
    }
    }
    }
    public void removeProductFromCart(最终int产品ID,最终int用户ID){
    ShoppingCart ShoppingCart=shoppingCartDao.findByUserId(userId);
    Product Product=productDao.findById(productId);
    if(product.getShoppingCart()!=shoppingCart){
    抛出新的CustomException(“产品不在指定用户的购物车中”);
    }否则{
    product.setShoppingCart(空);
    }
    }
    /**@见http://stackoverflow.com/questions/659915#659939 */
    私有对象getCacheSyncObject(最终整数id){
    
    @Entity
    @Table
    public class Product {
        @ManyToOne
        @JoinColumn
        private ShoppingCart shoppingCart;
    }
    
    @Entity
    @Table
    public class ShoppingCart {
        @OneToOne
        @JoinColumn
        private User user;
    
        @OneToMany(mappedBy = "shoppingCart")
        private Set<Product> products;
    }
    
    public class ShoppingCartDao { /* persist, merge, remove, findById etc. */ }
    
    @Transactional
    public class ProductService() {
        private ConcurrentMap<Integer, Integer> locks = 
            new ConcurrentHashMap<Integer, Integer>();
    
        public void addProductToCart(final int productId, final int userId) {
            ShoppingCart shoppingCart = shoppingCartDao.findByUserId(userId);                               
            Product product = productDao.findById(productId);
            synchronized(getCacheSyncObject(productId)) {
                if (product.shoppingCart == null) {
                    product.setShoppingCart(shoppingCart);
                } else {
                    throw new CustomException("Product already reserved.");
                }
            }
        }
    
        public void removeProductFromCart(final int productId, final int userId) {
            ShoppingCart shoppingCart = shoppingCartDao.findByUserId(userId);
            Product product = productDao.findById(productId);
            if (product.getShoppingCart() != shoppingCart) {
                throw new CustomException("Product not in specified user's cart.");
            } else {
                product.setShoppingCart(null);
            }
        }
    
        /** @See http://stackoverflow.com/questions/659915#659939 */
        private Object getCacheSyncObject(final Integer id) {
          locks.putIfAbsent(id, id);
          return locks.get(id);
        }       
    }
    
    [{id:1, count:100}]
    
    [{user_id:1, product_id:1}, {user_id:2, product_id:1}]
    
    [{user_id:3, product_id:1}]
    
    select count as total from product where id = 1
    select count(*) as in_cart from cart where product_id = 1
    select count(*) as total_bought from bought where product_id = 1
    
    int count = total - (in_cart + total_bought);
    
     [
      {id:1, count:100, newly_arrived: 0, date: 23/02/2016}, 
      {id:1, count:80, newly_arrived: 50, date: 24/02/2016}
     ]
    
    select count+newly_arrived as total from product where id = 1 and date = today
    select count(*) as in_cart from cart where product_id = 1 and date = today
    select count(*) as total_bought from bought where product_id = 1 and date = today
    
    Session session = sessionFactory.openSession()
    Transaction transaction;
    boolean productTaken = false;
    
    try {
        transaction = session.beginTransaction();
        Product product = session.get(Product.class, id);
        if (product == null)
            throw ...
    
        Session.LockRequest lockRequest = session.buildLockRequest(LockOptions.UPGRADE);
        lockRequest.lock(product);
        productTaken = product.take();
        if (productTaken) {
            session.update(product);
            transaction.commit();
        }
    } finally {
        if (transaction != null && transaction.isActive())
            transaction.rollback();
        session.close();
    }
    
    UPDATE product SET productCount = productCount + 1 WHERE id = ?