3

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

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

例如,从一个线程执行此操作:

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在实体的 Class 中使用注解(带有一个额外的 DB 列)会导致大型事务 ( updatePrices()) 以OptimisticLockException. 但是,如果它发生在大量查询结果的末尾,这是非常昂贵的。有什么方法可以使我的查询(内部updatePrices())锁定相关行,从而导致内部线程sellInstrument()阻塞或中止引发异常(然后中止)?这会便宜得多。(据我了解,我在 Toplink Essentials 中没有悲观锁定)。

4

2 回答 2

3

线程安全

我对您管理EntityManager. 虽然 aEntityManagerFactory是线程安全的(并且应该在应用程序启动时创建一次),但 anEntityManager不是,您通常应该为EntityManager每个线程使用一个(或同步对它的访问,但我会为每个线程使用一个)。

并发

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

PS:请注意,悲观锁定在 JPA 1.0 中不支持,或者仅通过提供程序特定的扩展(它已添加到 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 查询。

于 2010-07-14T23:51:38.980 回答
2

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

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

于 2010-07-14T23:25:50.990 回答