我正在使用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中没有悲观锁定。)
答案 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语句。