jooq乐观锁定和datachangedexception:数据库记录已在record.update上更改

gupuwyp2  于 2021-08-09  发布在  Java
关注(0)|答案(2)|浏览(396)

问题摘要:
我有一个 case class Test04(...) 将来自移动客户端的值更新到数据库中。表格用途 tSt 类型字段 timestamptz 对于乐观锁定和 tSt 值是db中的当前值。所以我有唯一的身份证和最新信息 tSt . 所以应该可以更新数据库中的值。
case类被转换为 RecordTest04 要缩短数据库操作,请执行以下操作: record.update() 而不是dsl语句,每次在表中添加/删除新字段时,我都必须手动修改该语句。
由于某种原因 record.update() 投掷 org.jooq.exception.DataChangedException: Database record has been changed 细节:
我有以下sql:

-- for table04
create or replace function createUuid() returns uuid
    as 'SELECT md5(random()::text || clock_timestamp()::text)::uuid;'
    language sql
    stable;

create or replace function insertUuidT()
returns trigger as
$BODY$
begin
    if new.id is null then
        new.id = createUuid();
    end if;
    new.tSt = now();
    return new;
end
$BODY$
language 'plpgsql';

create or replace function updatePreventT()
returns trigger as
$BODY$
begin
    if new.id <> old.id then
        raise exception 'You cannot modify id. Current id: % Proposed id: %', OLD.id, NEW.id; -- USING ERRCODE='123';
    end if;
    new.tSt = now();
    return new;
end
$BODY$
language 'plpgsql';

drop table if exists test04 cascade;

create table test04 (
    id uuid not null,                               -- Unique, link to other tables
    intNotNull int not null,
    dateNotNull date not null,
    dateNull date,
    timestamptzNotNull timestamptz not null,
    timestamptzNull timestamptz,
    tSt timestamptz not null,                       -- timestamp for optimistic locking support
    primary key ( id )
);

create unique index test04_id on test04( id );

drop trigger if exists test04_insert ON test04;
create trigger test04_insert before insert on test04 for each row execute procedure insertUuidT(); --setUuid();

drop trigger if exists test04_update ON test04;
create trigger test04_update before update on test04 for each row execute procedure updatePreventT(); --preventIdChange();

db设置为:

val settings = new Settings()
    .withExecuteWithOptimisticLocking(true) // Defaults to false
    .withUpdatablePrimaryKeys(true) // Defaults to false, primary keys are not always internal
    .withReturnAllOnUpdatableRecord(true) // Defaults to false, return all db/JOOQ generated values.
    .withMapJPAAnnotations(false) // Defaults to true, annotations are not used
    //.withExecuteWithOptimisticLockingExcludeUnversioned(true) // Defaults to false

  val sqlDialect = SQLDialect.POSTGRES

  val jdbcDriverClass = Class.forName("org.postgresql.Driver")

代码是:

val connection = JooqTestConnectionPool.dataSource.getConnection
  val db = DBSettings.getDSLContext(connection) // Using Conf to create DSL.

  val uuid1 = UUID.fromString("0c6e629b-aa0c-43eb-82fd-645c565a689a")
  val t04_1 = Test04(uuid1, 1230, LocalDate.now(), Some(LocalDate.now().minusDays(100)), OffsetDateTime.now(), Some(OffsetDateTime.now().minusMinutes(10)),OffsetDateTime.now())

  db.deleteFrom(TEST04).where(TEST04.ID.eq(uuid1)).execute()

  val t04_1RecA = db.newRecord(TEST04, t04_1)
  val t04_1RecB = db.newRecord(TEST04, t04_1)
  println(s"t04_1RecA before insert\n${t04_1RecA}")
  val iResA = t04_1RecA.insert()
  println(s"t04_1RecA after insert\n${t04_1RecA}")
  if (iResA != 1) throw new RuntimeException(s"Invalid insert res A: ${iResA}")

  t04_1RecB.changed("id",false)
  t04_1RecB.setIntnotnull(800)
  t04_1RecB.setTst(t04_1RecA.getTst)
  println(s"t04_1RecB before update\n${t04_1RecB}")
  t04_1RecB.update() // -> This will cause org.jooq.exception.DataChangedException: Database record has been changed
  println(s"t04_1RecB after update\n${t04_1RecB}")

  connection.close()

运行时输出为:

Thank you for using jOOQ 3.12.4

Executing query          : delete from "public"."test04" where "public"."test04"."id" = cast(? as uuid)
-> with bind values      : delete from "public"."test04" where "public"."test04"."id" = '0c6e629b-aa0c-43eb-82fd-645c565a689a'
Affected row(s)          : 1
t04_1RecA before insert
+-------------------------------------+----------+-----------+-----------------+---------------------------------+---------------------------------------+---------------------------------+
|id                                   |intnotnull|datenotnull|datenull         |timestamptznotnull               |timestamptznull                        |tst                              |
+-------------------------------------+----------+-----------+-----------------+---------------------------------+---------------------------------------+---------------------------------+
|*0c6e629b-aa0c-43eb-82fd-645c565a689a|     *1230|*2020-06-12|*Some(2020-03-04)|*2020-06-12T15:59:39.655505+03:00|*Some(2020-06-12T15:49:39.655508+03:00)|*2020-06-12T15:59:39.655514+03:00|
+-------------------------------------+----------+-----------+-----------------+---------------------------------+---------------------------------------+---------------------------------+

Executing query          : insert into "public"."test04" ("id", "intnotnull", "datenotnull", "datenull", "timestamptznotnull", "timestamptznull", "tst") values (cast(? as uuid), ?, cast(? as date), cast(? as date), cast(? as timestamp with time zone), cast(? as timestamp with time zone), cast(? as timestamp with time zone)) returning "public"."test04"."id", "public"."test04"."intnotnull", "public"."test04"."datenotnull", "public"."test04"."datenull", "public"."test04"."timestamptznotnull", "public"."test04"."timestamptznull", "public"."test04"."tst"
-> with bind values      : insert into "public"."test04" ("id", "intnotnull", "datenotnull", "datenull", "timestamptznotnull", "timestamptznull", "tst") values ('0c6e629b-aa0c-43eb-82fd-645c565a689a', 1230, date '2020-06-12', date '2020-03-04', timestamp with time zone '2020-06-12 15:59:39.655505+03:00', timestamp with time zone '2020-06-12 15:49:39.655508+03:00', timestamp with time zone '2020-06-12 15:59:39.655514+03:00') returning "public"."test04"."id", "public"."test04"."intnotnull", "public"."test04"."datenotnull", "public"."test04"."datenull", "public"."test04"."timestamptznotnull", "public"."test04"."timestamptznull", "public"."test04"."tst"
 Fetched result           : +------------------------------------+----------+-----------+----------------+--------------------------------+--------------------------------------+--------------------------------+
                          : |id                                  |intnotnull|datenotnull|datenull        |timestamptznotnull              |timestamptznull                       |tst                             |
                          : +------------------------------------+----------+-----------+----------------+--------------------------------+--------------------------------------+--------------------------------+
                          : |0c6e629b-aa0c-43eb-82fd-645c565a689a|      1230|2020-06-12 |Some(2020-03-04)|2020-06-12T15:59:39.655505+03:00|Some(2020-06-12T15:49:39.655508+03:00)|2020-06-12T15:59:40.181434+03:00|
                          : +------------------------------------+----------+-----------+----------------+--------------------------------+--------------------------------------+--------------------------------+
 Fetched row(s)           : 1
t04_1RecA after insert
+------------------------------------+----------+-----------+----------------+--------------------------------+--------------------------------------+--------------------------------+
|id                                  |intnotnull|datenotnull|datenull        |timestamptznotnull              |timestamptznull                       |tst                             |
+------------------------------------+----------+-----------+----------------+--------------------------------+--------------------------------------+--------------------------------+
|0c6e629b-aa0c-43eb-82fd-645c565a689a|      1230|2020-06-12 |Some(2020-03-04)|2020-06-12T15:59:39.655505+03:00|Some(2020-06-12T15:49:39.655508+03:00)|2020-06-12T15:59:40.181434+03:00|
+------------------------------------+----------+-----------+----------------+--------------------------------+--------------------------------------+--------------------------------+

t04_1RecB before update
+------------------------------------+----------+-----------+-----------------+---------------------------------+---------------------------------------+---------------------------------+
|id                                  |intnotnull|datenotnull|datenull         |timestamptznotnull               |timestamptznull                        |tst                              |
+------------------------------------+----------+-----------+-----------------+---------------------------------+---------------------------------------+---------------------------------+
|0c6e629b-aa0c-43eb-82fd-645c565a689a|      *800|*2020-06-12|*Some(2020-03-04)|*2020-06-12T15:59:39.655505+03:00|*Some(2020-06-12T15:49:39.655508+03:00)|*2020-06-12T15:59:40.181434+03:00|
+------------------------------------+----------+-----------+-----------------+---------------------------------+---------------------------------------+---------------------------------+

 Executing query          : select "public"."test04"."id", "public"."test04"."intnotnull", "public"."test04"."datenotnull", "public"."test04"."datenull", "public"."test04"."timestamptznotnull", "public"."test04"."timestamptznull", "public"."test04"."tst" from "public"."test04" where "public"."test04"."id" = cast(? as uuid) for update
 -> with bind values      : select "public"."test04"."id", "public"."test04"."intnotnull", "public"."test04"."datenotnull", "public"."test04"."datenull", "public"."test04"."timestamptznotnull", "public"."test04"."timestamptznull", "public"."test04"."tst" from "public"."test04" where "public"."test04"."id" = '0c6e629b-aa0c-43eb-82fd-645c565a689a' for update
 Fetched result           : +------------------------------------+----------+-----------+----------------+--------------------------------+--------------------------------------+--------------------------------+
                          : |id                                  |intnotnull|datenotnull|datenull        |timestamptznotnull              |timestamptznull                       |tst                             |
                          : +------------------------------------+----------+-----------+----------------+--------------------------------+--------------------------------------+--------------------------------+
                          : |0c6e629b-aa0c-43eb-82fd-645c565a689a|      1230|2020-06-12 |Some(2020-03-04)|2020-06-12T15:59:39.655505+03:00|Some(2020-06-12T15:49:39.655508+03:00)|2020-06-12T15:59:40.181434+03:00|
                          : +------------------------------------+----------+-----------+----------------+--------------------------------+--------------------------------------+--------------------------------+
 Fetched row(s)           : 1
 Concurrent update 1 1677ms 
  org.jooq.exception.DataChangedException: Database record has been changed
    org.jooq.impl.UpdatableRecordImpl.checkIfChanged(UpdatableRecordImpl.java:427)
    org.jooq.impl.UpdatableRecordImpl.storeUpdate0(UpdatableRecordImpl.java:247)

问题是 t04_1RecB.update() 尽管 tst 用于乐观锁定的字段在更新之前立即从db复制。
如果我使用 .withExecuteWithOptimisticLockingExcludeUnversioned(true) 然后更新不会失败,但是更新不会使用 tst 列以检测过期更新。

jfgube3f

jfgube3f1#

我用两个表进一步测试了这个问题。第一个表(test07)有 timestamp 键入用于乐观锁定的列。jooq文件提到 int 以及 timestamp 乐观锁定字段的类型。在我最初的环境中,乐观的锁定列是 timestamptz . 然而,这并没有解决问题。会引发完全相同的异常。
第二个表(test03)有 int 乐观锁定列:

-- for table 03
create or replace function insertUuidVerT()
returns trigger as
$BODY$
begin
    if new.id is null then
        new.id = createUuid();
    end if;
    new.ver = 0;
    return new;
end
$BODY$
language 'plpgsql';

create or replace function updatePreventVerT()
returns trigger as
$BODY$
begin
    if new.id <> old.id then
        raise exception 'You cannot modify id. Current id: % Proposed id: %', OLD.id, NEW.id; -- USING ERRCODE='123';
    end if;
    new.ver = old.ver + 1;
    return new;
end
$BODY$
language 'plpgsql';

drop table if exists test03 cascade;

create table test03 (
    id uuid not null,                               -- Unique, link to other tables
    intNotNull int not null,
    dateNotNull date not null,
    dateNull date,
    timestamptzNotNull timestamptz not null,
    timestamptzNull timestamptz,
    ver int not null default 0,                     -- version for optimistic locking support
    primary key ( id )
);

drop trigger if exists test03_insert ON test03;
create trigger test03_insert before insert on test03 for each row execute procedure insertUuidVerT(); --setUuid();

drop trigger if exists test03_update ON test03;
create trigger test03_update before update on test03 for each row execute procedure updatePreventVerT(); --preventIdChange();

jooq设置与前面相同,scala代码是:

val connection = JooqTestConnectionPool.dataSource.getConnection
  val db = DBSettings.getDSLContext(connection) //conf) // Using Conf to create DSL.

  val t03_1 = Test03(UUID.fromString("0bec53ff-91cb-47be-8318-14dd298e1d00"), 1230, LocalDate.now(), Some(LocalDate.now()), OffsetDateTime.now(), Some(OffsetDateTime.now()), null)

  db.deleteFrom(TEST03).where(TEST03.ID.eq(t03_1.id)).execute()

  val t03_1RecA = db.newRecord(TEST03, t03_1)
  val t03_1RecB = db.newRecord(TEST03, t03_1) // Identical id values
  println(s"t03_1RecA before insert\n${t03_1RecA}")
  val iResA = t03_1RecA.insert()
  println(s"t03_1RecA after insert\n${t03_1RecA}")
  if (iResA != 1) throw new RuntimeException(s"Invalid insert res A: ${iResA}")

  t03_1RecB.changed("id",false) // Does not work without this
  t03_1RecB.setIntnotnull(800)
  t03_1RecB.setVer(t03_1RecA.getVer)
  t03_1RecB.changed("ver",false) // Does not work without this
  println(s"t03_1RecB before update\n${t03_1RecB}")
  t03_1RecB.update()
  println(s"t03_1RecB after update\n${t03_1RecB}")

  connection.close()

运行时输出:

Executing query          : insert into "public"."test03" ("id", "intnotnull", "datenotnull", "datenull", "timestamptznotnull", "timestamptznull", "ver") values (cast(? as uuid), ?, cast(? as date), cast(? as date), cast(? as timestamp with time zone), cast(? as timestamp with time zone), ?) returning "public"."test03"."id", "public"."test03"."intnotnull", "public"."test03"."datenotnull", "public"."test03"."datenull", "public"."test03"."timestamptznotnull", "public"."test03"."timestamptznull", "public"."test03"."ver"
 -> with bind values      : insert into "public"."test03" ("id", "intnotnull", "datenotnull", "datenull", "timestamptznotnull", "timestamptznull", "ver") values ('0bec53ff-91cb-47be-8318-14dd298e1d00', 1230, date '2020-06-13', date '2020-06-13', timestamp with time zone '2020-06-13 16:12:49.455824+03:00', timestamp with time zone '2020-06-13 16:12:49.455849+03:00', 1) returning "public"."test03"."id", "public"."test03"."intnotnull", "public"."test03"."datenotnull", "public"."test03"."datenull", "public"."test03"."timestamptznotnull", "public"."test03"."timestamptznull", "public"."test03"."ver"
 Fetched result           : +------------------------------------+----------+-----------+----------------+--------------------------------+--------------------------------------+----+
                          : |id                                  |intnotnull|datenotnull|datenull        |timestamptznotnull              |timestamptznull                       | ver|
                          : +------------------------------------+----------+-----------+----------------+--------------------------------+--------------------------------------+----+
                          : |0bec53ff-91cb-47be-8318-14dd298e1d00|      1230|2020-06-13 |Some(2020-06-13)|2020-06-13T16:12:49.455824+03:00|Some(2020-06-13T16:12:49.455849+03:00)|   0|
                          : +------------------------------------+----------+-----------+----------------+--------------------------------+--------------------------------------+----+
 Fetched row(s)           : 1
t03_1RecA after insert
+------------------------------------+----------+-----------+----------------+--------------------------------+--------------------------------------+----+
|id                                  |intnotnull|datenotnull|datenull        |timestamptznotnull              |timestamptznull                       | ver|
+------------------------------------+----------+-----------+----------------+--------------------------------+--------------------------------------+----+
|0bec53ff-91cb-47be-8318-14dd298e1d00|      1230|2020-06-13 |Some(2020-06-13)|2020-06-13T16:12:49.455824+03:00|Some(2020-06-13T16:12:49.455849+03:00)|   0|
+------------------------------------+----------+-----------+----------------+--------------------------------+--------------------------------------+----+

t03_1RecB before update
+------------------------------------+----------+-----------+-----------------+---------------------------------+---------------------------------------+----+
|id                                  |intnotnull|datenotnull|datenull         |timestamptznotnull               |timestamptznull                        | ver|
+------------------------------------+----------+-----------+-----------------+---------------------------------+---------------------------------------+----+
|0bec53ff-91cb-47be-8318-14dd298e1d00|      *800|*2020-06-13|*Some(2020-06-13)|*2020-06-13T16:12:49.455824+03:00|*Some(2020-06-13T16:12:49.455849+03:00)|   0|
+------------------------------------+----------+-----------+-----------------+---------------------------------+---------------------------------------+----+

 Executing query          : update "public"."test03" set "intnotnull" = ?, "datenotnull" = cast(? as date), "datenull" = cast(? as date), "timestamptznotnull" = cast(? as timestamp with time zone), "timestamptznull" = cast(? as timestamp with time zone), "ver" = ? where ("public"."test03"."id" = cast(? as uuid) and "public"."test03"."ver" = ?) returning "public"."test03"."id", "public"."test03"."intnotnull", "public"."test03"."datenotnull", "public"."test03"."datenull", "public"."test03"."timestamptznotnull", "public"."test03"."timestamptznull", "public"."test03"."ver"
 -> with bind values      : update "public"."test03" set "intnotnull" = 800, "datenotnull" = date '2020-06-13', "datenull" = date '2020-06-13', "timestamptznotnull" = timestamp with time zone '2020-06-13 16:12:49.455824+03:00', "timestamptznull" = timestamp with time zone '2020-06-13 16:12:49.455849+03:00', "ver" = 1 where ("public"."test03"."id" = '0bec53ff-91cb-47be-8318-14dd298e1d00' and "public"."test03"."ver" = 0) returning "public"."test03"."id", "public"."test03"."intnotnull", "public"."test03"."datenotnull", "public"."test03"."datenull", "public"."test03"."timestamptznotnull", "public"."test03"."timestamptznull", "public"."test03"."ver"
 Fetched result           : +------------------------------------+----------+-----------+----------------+--------------------------------+--------------------------------------+----+
                          : |id                                  |intnotnull|datenotnull|datenull        |timestamptznotnull              |timestamptznull                       | ver|
                          : +------------------------------------+----------+-----------+----------------+--------------------------------+--------------------------------------+----+
                          : |0bec53ff-91cb-47be-8318-14dd298e1d00|       800|2020-06-13 |Some(2020-06-13)|2020-06-13T16:12:49.455824+03:00|Some(2020-06-13T16:12:49.455849+03:00)|   1|
                          : +------------------------------------+----------+-----------+----------------+--------------------------------+--------------------------------------+----+
 Fetched row(s)           : 1
t03_1RecB after update
+------------------------------------+----------+-----------+----------------+--------------------------------+--------------------------------------+----+
|id                                  |intnotnull|datenotnull|datenull        |timestamptznotnull              |timestamptznull                       | ver|
+------------------------------------+----------+-----------+----------------+--------------------------------+--------------------------------------+----+
|0bec53ff-91cb-47be-8318-14dd298e1d00|       800|2020-06-13 |Some(2020-06-13)|2020-06-13T16:12:49.455824+03:00|Some(2020-06-13T16:12:49.455849+03:00)|   1|
+------------------------------------+----------+-----------+----------------+--------------------------------+--------------------------------------+----+

所以在使用 int 键入乐观锁定字段此异常不会发生,但如果使用 timestamptz 或者 timestamp 因为某些原因它会发生。
更换 timestamptzint 这是一个解决方案,但我仍然想了解的问题是什么 timestamptz .

g9icjywg

g9icjywg2#

伟大的调查!不确定这是否是预期的语义-对于 timestampz 数据类型。你能为这个打开一个github问题,我们来看看吗?

相关问题