Hibernate 事务计数器行为

Hibernate Transaction Counter Behaviour

我有以下 JPA 实体:

@Entity(name="metrics")
public class Metrics {

    @Id
    private String metricId;

    @Column
    private long count;

    public Metrics() {
        count = 0;
    }

我尝试像这样自动更新它:

//Begin transaction
Metrics result = em.find(Metrics.class, id);

if (result == null) {
    result = new Metrics();
    result.metricId = id;
    result.count++;
    em.persist(result);
} else {
    result.count++;
    em.merge(result);
}
//Commit transaction

然而,出于某种原因,这似乎并没有使更新成为原子更新,我最终在并发环境中丢失了更新。我可以通过使用 @Version 通过 Hibernate 实现乐观锁定来解决这个问题,但我有点惊讶这是必需的。

为什么上面的代码即使有事务也会丢失更新?

原子意味着一次只有一个操作。 java 语言规范保证读取或写入变量是原子的 除非变量是 longdouble[=26= 类型]。 因为变量 (long/double) 是使用两个单独的操作写入的:一个写入前 32 位,第二个写入最后 32 位。这意味着另一个线程可能会读取 count 的值并查看中间状态。

使此操作成为原子操作的一种简单方法是使变量成为易变的:

private volatile long count;

现在,count 永远不会被任何 Thread 缓存,并且可以确保这个变量将始终被主内存读取,并且在并发环境中永远不会丢失您的计数.

AtomicLong 您可能也会感兴趣,因为您不会在实体中使用它 class。

你是对的,如果你想在数据库中准确保存有效值,只有乐观或悲观锁定可以帮助你。在您的情况下,悲观锁定会更好,因为应用程序在竞争环境中工作,但乐观锁定会一次又一次地抛出异常。应用服务器重启或挂起后,Atomic 的所有优势都将丢失。