代码之家  ›  专栏  ›  技术社区  ›  TofuBeer

多线程应用程序的JPA原子查询/保存

  •  8
  • TofuBeer  · 技术社区  · 14 年前

    我正在修改我的JPA代码以使用线程。我为每个线程都有一个单独的实体管理器和事务。

    我以前(对于单线程环境)使用的代码如下:

    // get object from the entity manager
    X x = getObjectX(jpaQuery);
    
    if(x == null)
    {
        x = new X();
        x.setVariable(foo);
        entityManager.persist(x);
    }
    

    对于多线程环境中的代码,我得到了重复的键,因为我假设getObjectX为一个线程返回空值,然后该线程被交换,下一个线程调用getObjextx,也得到空值,然后两个线程都将创建并保持一个新的x()。

    除了添加同步之外,如果JPA中不存在一个值,是否有一种原子方法来获取/保存,或者我应该重新考虑我的方法

    编辑:

    我正在使用最新的EclipseLink和MySQL5.1

    编辑2:

    我添加了同步…巨大的性能冲击(达到无法使用的程度)。将收集主线程上的所有数据,然后在该线程上进行创建。

    4 回复  |  直到 14 年前
        1
  •  4
  •   Affe    14 年前

    简短而悲伤的回答是:不是JPA API不能为您做这些。整个API或多或少都是基于乐观的原则构建的,即假设事情能够正常工作,并在发生并发修改时抛出异常。

    如果经常发生这种情况,那么很可能还有其他组件(任何生成foo的组件?)这可能得益于创建threadsafe,可能是围绕query+create进行同步的替代方法。

        2
  •  3
  •   Bozho    14 年前

    一些“黑客”需要考虑:

    • 实施 hashCode() equals() 基于对象的业务密钥(而不是生成的ID)
    • 同步:

      (obj.getClass().getName() + String.valueOf(hashCode())).intern()
      

    因此,您只能在相关的情况下获得锁。

        3
  •  2
  •   Michael Barker    14 年前

    我认为您需要在“jpaQuery”中使用的字段上添加一个唯一的约束,这样数据库就不能使用该查询的约束中使用的相同条件创建重复的行。调用代码将需要捕获由于违反约束而产生的异常(理想情况下,它将是EntityExistsException,但在这种情况下规范不明确)。

        4
  •  0
  •   Sean Patrick Floyd    14 年前

    您确定需要多个实体经理吗?在类似的情况下,我只使用一个EntityManager和每个方法的简单锁对象:

    private Object customerLock = new Object[0];
    
    public Customer createCustomer(){
        Customer customer = new Customer();
        synchronized(customerLock){
            entityManager.persist(customer);
        }
        return customer;
    }
    

    编辑:好的,除了说它在我的应用程序中运行正常外,不能对性能做太多的事情,但是为了唯一性,请使用如下方法:

    public Customer getOrCreateCustomer(String firstName, String lastName){
        synchronized(customerLock){
            List<Customer> customers = 
                entityManager.createQuery(
                    "select c from Customer c where c.firstName = :firstName"
                    + " and c.lastName = :lastName"
                )
                .setParam("firstName", firstName)
                .setParam("lastName", lastName)
                .setMaxResults(1)
                .getResultList();
            if(customers.isEmpty()){
                Customer customer = new Customer(firstName, lastName);
                entityManager.persist(customer);
            }else{
                customer = customers.get(0);
            }
        }
        return customer;
    }