问题总结:
我有一个case class Test04(...)
来自例如移动客户端的值要更新到数据库中。表使用tSt
类型的字段timestamptz
进行乐观锁定,tSt
值是数据库中的当前值。所以我有唯一的 id 和最新的tSt
。所以应该可以更新数据库中的值。
案例类被转换为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();
数据库设置为:
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()
的字段是在更新前立即从 db 复制的,但仍会引发异常。tst
如果我使用.withExecuteWithOptimisticLockingExcludeUnversioned(true)
,则更新不会失败,但更新不会使用tst
列来检测过时的更新。