问题摘要:
我有一个case class Test04(...)
,其值例如移动客户端将更新为数据库。表使用类型为tSt
的timestamptz
字段进行乐观锁定,并且tSt
值为数据库中的当前值。因此,我具有唯一的ID,并且是最新的tSt
。因此应该可以更新数据库中的值。
案例类被转换为RecordTest04
以使数据库操作更短:record.update()
而不是DSL语句,每次将新字段添加到表中/从表中删除时,我都必须手动修改它。
由于某种原因record.update()
抛出org.jooq.exception.DataChangedException: Database record has been changed
详细信息:
我有以下sql:
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();
数据库设置是:
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
列来检测过期更新。
我使用两个表对此问题进行了进一步的测试。第一个表(test07)具有用于乐观锁定的timestamp
类型列。 JOOQ文档提到了乐观锁定字段的int
和timestamp
类型。在我的原始环境中,乐观锁定列的类型为timestamptz
。但是,这不能解决问题。引发完全相同的异常。
第二张表(test03)的int
列用于乐观锁定:
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
字段,则会由于某种原因而发生。
用timestamptz
替换int
是一种解决方案,但是我仍然想了解timestamptz
的问题。