如何在Java SE中使用持久性API防止不可重复的查询结果?

时间:2010-07-14 22:55:59

标签: jpa persistence entitymanager java

我正在使用Java SE并学习使用持久性API(toplink-essentials)来管理Derby DB中的实体。注意:这是(远程学习)大学工作,但不是“作业”这个问题在课程材料中出现。

我有两个线程在同一组实体上运行。我的问题是,我尝试过的每一种方法都可以修改一个线程中查询结果集(在事务中执行的查询)中的实体,以便结果集对事务的其余部分不再有效。

e.g。从一个线程执行此操作:

static void updatePrices(EntityManager manager, double percentage) {
    EntityTransaction transaction = manager.getTransaction();

    transaction.begin();
    Query query = manager.createQuery("SELECT i FROM Instrument i where i.sold = 'no'");
    List<Instrument> results = (List<Instrument>) query.getResultList();

    // force thread interruption here (testing non-repeatable read)
    try { Thread.sleep(2000); } catch (Exception e) { }

    for (Instrument i : results) {
        i.updatePrice(percentage);
    }
    transaction.commit();
    System.out.println("Price update commited");
}

如果它使用此方法从另一个线程中断:

private static void sellInstrument(EntityManager manager, int id)
{
    EntityTransaction transaction = manager.getTransaction();
    transaction.begin();
    Instrument instrument = manager.find(Instrument.class, id);
    System.out.println("Selling: " + instrument.toFullString());
    instrument.setSold(true);
    transaction.commit();
    System.out.println("Instrument sale commited");
}

updatePrices()内的线程恢复它的查询结果集无效时,可能会发生这种情况,并且售出商品的价格最终会更新为与其售出的价格不同的价格。 (商店希望保留在DB中销售的商品的记录)。由于发生了并发事务,我为每个线程(来自同一工厂)使用不同的EntityManager

是否可以(通过锁定或某种上下文传播)防止查询结果在(中断)事务期间变为“无效”?我知道这种情况是Java EE的用途,但我想知道的是它是否可以在Java SE中使用。


编辑:

采用Vineet和Pascal的建议:在实体的类中使用@Version注释(带有额外的DB列)会导致大事务(updatePrices())失败并显示OptimisticLockException。如果它发生在大量查询结果的末尾,这是非常昂贵的。有没有办法让我的查询(在updatePrices()内)锁定相关的行,导致sellInstrument()内的线程阻塞或中止抛出异常(然后中止)?这会便宜得多。 (根据我的理解,我在Toplink Essentials中没有悲观锁定。)

2 个答案:

答案 0 :(得分:3)

线程安全

我对您管理EntityManager的方式存有疑问。虽然EntityManagerFactory是线程安全的(并且应该在应用程序启动时创建一次),但是EntityManager不是,并且您通常应该为每个线程使用一个EntityManager(或同步对它的访问)但我会在每个帖子中使用一个。)

并发

JPA 1.0支持(仅)乐观锁定(如果使用Version属性)和两种锁定模式,允许避免脏读不可重复读取通过EntityManager.lock() API。我建议阅读JPA 1.0规范的Read and Write Locking和/或整个 3.4 Optimistic Locking and Concurrency 部分以获取完整的详细信息。

PS:请注意,JPA 1.0中不支持Pessimistic locking或仅通过提供程序特定的扩展(它已添加到JPA 2.0以及其他锁定选项)。以防万一,Toplink通过eclipselink.pessimistic-lock查询提示支持它。


正如JPA wiki所述,TopLink Essentials应该通过查询提示支持JPA 1.0中的悲观锁定:

// eclipselink.pessimistic-lock
Query Query = em.createQuery("select f from Foo f where f.bar=:bar");
query.setParameter("bar", "foobar");
query.setHint("eclipselink.pessimistic-lock", "Lock");
query.getResultList();

我不使用TopLink,所以我无法确认所有版本都支持此提示。如果不是,那么如果要生成“FOR UPDATE”,则必须使用本机SQL查询。

答案 1 :(得分:2)

您可能希望查看EntityManager.lock()方法,该方法允许您在初始化事务后获得对实体的乐观或悲观锁定。

根据您对问题的描述,您希望在数据库记录从数据库中“选中”后锁定它。这可以通过悲观锁实现,它或多或少等同于SELECT ... FROM tbl FOR UPDATE语句。