【问题标题】:How to prevent non-repeatable query results using persistence API in Java SE?如何使用 Java SE 中的持久性 API 防止不可重复的查询结果?
【发布时间】:2011-03-16 03:18:02
【问题描述】:

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

【问题讨论】:

    标签: jpa persistence entitymanager java


    【解决方案1】:

    线程安全

    我对您管理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:请注意,Pessimistic locking 在 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 查询。

    【讨论】:

    • 感谢 Pascal 的指点,FWIW 我每个线程使用一个 EntityManager(都来自同一个 EntityManagerFactory)。由于我目前坚持使用 toplink-essentials/JPA 1.0,因此我将尝试进行乐观锁定。
    • 再次感谢,我被 JPA 1.0 规范弄糊涂了,认为只有 Toplink 为产品付费才能进行悲观锁定。
    【解决方案2】:

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

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

    【讨论】:

    • 感谢 Vineet,第一次回答我的第一个 SO 问题!顺便说一句,我还没有能力投票!在这种情况下,我需要在 updatePrices() 中锁定所有“选定”记录/实体,但是如何确保在另一个线程中断并修改其中一个选定记录之前获得锁定?你知道有一种方法可以让锁在查询中自动发生吗?
    • 您可能需要在所有此类场景中获取锁,而不仅仅是一个。这样,如果任何线程在另一个线程之前获得锁,第二个事务实际上将被第二个处理。您必须将其留给 JPA 提供程序和数据库来对事务顺序进行排序。尝试自己对交易进行排序没有任何好处;只需在查询对象之前获取锁,就可以了。然而,这带来了确保锁不会被持有太久的有趣部分。那将是另一个问题...
    • 问题是,这里有一个大事务和一个小事务同时发生。如果可能的话,我希望小事务 (sellInstrument() ) 由于访问已经是另一个事务查询结果的一部分的实体而失败。
    • @willijcroz:我会为你投票,因为这个帖子也对我有很大帮助:D 正在讨论我正在寻找的东西。
    猜你喜欢
    • 2011-03-15
    • 2018-05-18
    • 2018-07-10
    • 2014-03-27
    • 2016-03-16
    • 1970-01-01
    • 2011-08-03
    • 2010-10-31
    • 2011-02-03
    相关资源
    最近更新 更多