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

Java 多线程应用程序的JPA原子查询/保存,java,jpa,Java,Jpa,我正在修改我的JPA代码以利用线程。我有一个单独的实体管理器和每个线程的事务 我以前(对于单线程环境)的代码如下: // get object from the entity manager X x = getObjectX(jpaQuery); if(x == null) { x = new X(); x.setVariable(foo); entityManager.persist(x); } 对于多线程环境中的代码,我得到了重复的键,因为我假设getObject

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

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

// get object from the entity manager
X x = getObjectX(jpaQuery);

if(x == null)
{
    x = new X();
    x.setVariable(foo);
    entityManager.persist(x);
}
对于多线程环境中的代码,我得到了重复的键,因为我假设getObjectX为一个线程返回null,然后该线程被交换出去,下一个线程调用getObjectX,也得到null,然后两个线程将创建并持久化一个新的X()

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

编辑:

我正在使用最新的Eclipselink和MySql 5.1

编辑2:


我添加了同步。。。巨大的性能损失(以至于无法使用)。将在主线程上收集所有数据,然后在该线程上进行创建。

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

是否确实需要多个EntityManager?在类似的情况下,我只使用一个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;
}
public Customer getOrCreateCustomer(String firstName,String lastName){
已同步(customerLock){
列出客户=
entityManager.createQuery(
“从客户c中选择c,其中c.firstName=:firstName”
+“和c.lastName=:lastName”
)
.setParam(“名字”,名字)
.setParam(“lastName”,lastName)
.setMaxResults(1)
.getResultList();
if(customers.isEmpty()){
客户=新客户(名字、姓氏);
entityManager.persist(客户);
}否则{
customer=customers.get(0);
}
}
退货客户;
}

简而言之,悲哀的答案是否定的,JPAAPI不能为您做到这一点。整个API或多或少都是围绕着乐观的原则构建的,即假设一切正常,并在并发修改的情况下抛出异常

如果这种情况经常发生,那么可能还有其他组件(生成foo的是什么?)可以从线程安全中获益,作为围绕查询+创建进行同步的替代方案。

需要考虑一些“黑客”问题:

  • 基于对象的业务密钥(而不是生成的id)实现
    hashCode()
    equals()
  • 同步时间:

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

因此,您将仅在相关情况下获得锁。

我确实有唯一的约束,我得到了例外。我希望找到一种不引发异常的方法…在这种情况下,使用异常可能是最简单的解决方案,类似于乐观锁定,在失败时也使用异常。我需要确保对象(在您的情况下)不存在客户。我尝试添加同步,但性能非常差。我必须反过来做,创建Foo的东西是我无法控制的,而且几乎可以保证会有重复的。我必须收集线程中的所有数据,然后将它们存储在单个线程中。嗯。。。我已经有了这样的等号/哈希码。。。从未想过在它们上同步。我要试试看。