Hibernate 乐观锁

来自 PostgreSQL wiki
跳转到导航跳转到搜索

片段

Hibernate 乐观锁

适用于 PostgreSQL

任何版本

用什么写的

SQL

依赖


Hibernate Java ORM 工具使用基于字段的行版本乐观锁,而不是使用数据库内置的事务行锁。这样做是为了允许非常短的事务,在这些事务中,事务不需要在用户“思考时间”期间保持打开状态,从而阻止其他等待已持有锁的事务,并导致 VACUUM 问题。但是,这意味着 Hibernate 会话不会阻止其他数据库写入器在 Hibernate 读取记录和 Hibernate 稍后在不同的事务中写入已更改记录(带有增量的版本计数器)之间对记录进行更改。

例如,给定一个表

CREATE TABLE tab (
  id INTEGER PRIMARY KEY,
  somefield text not null
);
INSERT INTO tab(id, somefield) VALUES (1,'fred');

以下操作序列(时间线向下)

Hibernate其他写入器
BEGIN; SELECT id, somefield FROM tab WHERE id = 1; COMMIT; 
 BEGIN; UPDATE tab SET somefield = 'foo' WHERE id = 1; COMMIT;
BEGIN; UPDATE tab SET somefield = 'baz' WHERE id = 1; COMMIT; 

... 将导致“其他写入器”的 UPDATE 丢失,并且“somefield”的最终值为“baz”。Hibernate 复制了记录,对其进行了修改,并在单独的事务中将其写回 - 这正是 SQL 中的典型错误。但是,Hibernate 针对该问题有一个内置的解决方法,使用行版本来进行乐观锁。使用 Hibernate 的乐观锁,我们将获得

CREATE TABLE tab (
  id INTEGER PRIMARY KEY,
  somefield text not null,
  rowversion INTEGER NOT NULL DEFAULT(0)
);
INSERT INTO tab(id, somefield) VALUES (1,'fred');
Hibernate其他写入器
BEGIN; SELECT id, somefield, oplock FROM tab WHERE id = 1; COMMIT;
BEGIN; UPDATE tab SET somefield = 'foo' WHERE id = 1; COMMIT;
BEGIN; UPDATE tab SET somefield = 'baz', rowversion = 1 WHERE id = 1 AND rowversion = 0; COMMIT;

Hibernate 期望行版本在对记录进行任何其他写入时都会递增,因此它预计它的 UPDATE 会失败,因为 WHERE 子句中的“rowversion = 0”部分将无法匹配。但是,由于其他写入器不知道 Hibernate 的预期,并且没有更新 rowversion 列,因此它的更改会被 Hibernate 覆盖。

与其更改所有其他客户端以了解行版本(仅仅因为你引入了基于 Hibernate 的客户端),不如引入一个触发器,在 UPDATE 期间测试行版本是否已递增,如果未递增,则确保应用递增。

CREATE OR REPLACE FUNCTION zz_row_version() RETURNS trigger AS $$
BEGIN
    IF tg_op = 'UPDATE'
       AND new.rowversion = old.rowversion
       AND ROW(new.*) IS DISTINCT FROM ROW (old.*)
    THEN
        -- Row is being updated by an application that does not know
        -- about row versioning. It's changed data in the row, but hasn't
        -- incremented the version. We'll do that for it.
        new.rowversion := new.rowversion + 1;
    END IF;
    RETURN new;
END;
$$ LANGUAGE 'plpgsql';

COMMENT ON FUNCTION zz_field_version() IS 'Increments the record version if a row is changed by an update and '
'its version was not incremented by the UPDATE issuer. Allows ORM rowvers oplocking like Hibernate to coexist '
'with normal DB transactional locking. '
'Target tables must have an integral not-null field named rowversion with a default of 0.';

(根据需要更改版本列名称 - 它通常称为“oplock”。)

由 --Ringerc 05:11, 5 November 2009 (UTC) (Craig Ringer)