jOOQ 无法在 PostgreSQL 模式下为 H2 数据库正确翻译 SQL
jOOQ does not translate SQL properly for H2 database in PostgreSQL mode
问题出在 Postgres ON CONFLICT
语法上。
版本(maven 依赖项):
- postgresql: 42.2.9
- jooq: 3.12.3
- h2数据库:1.4.200
// mocking connection
final Connection connection = DriverManager.getConnection("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;Mode=PostgreSQL", "sa", "");
final Settings settings = new Settings().withRenderNameStyle(RenderNameStyle.AS_IS);
Mockito.doReturn(DSL.using(connection, SQLDialect.POSTGRES, settings)).when(mockDbConn).getDSLContext();
// java code for upsert:
dc.insertInto(MY_TABLE)
.columns(MY_TABLE.TOKEN, MY_TABLE.NAME, MY_TABLE.EMAIL)
.values(token, name, email)
.onDuplicateKeyUpdate()
.set(MY_TABLE.EMAIL, email)
.execute();
得到以下错误日志(似乎问题可能是因为[*]
(第4行↓)。我不明白为什么会出现以及如何删除它):
-- Syntax error in SQL statement:
INSERT INTO PUBLIC.MY_TABLE (TOKEN, NAME, EMAIL)
VALUES (?, ?, ?)
ON CONFLICT ([*]TOKEN, NAME) -- line 4
DO UPDATE SET EMAIL = EXCLUDED.EMAIL;
-- expected "DO";
-- SQL statement:
insert into public.my_table (token, name, email)
values (?, ?, ?)
on conflict (token, name)
do update set email = excluded.email;
-- [42001-200]
如果我将方言从 SQLDialect.POSTGRES
切换到 SQLDialect.H2
会发生以下情况:
-- Column "EXCLUDED.EMAIL" not found; SQL statement:
merge into public.my_table using (select 1 one)
on (public.my_table.token = cast(? as varchar) and public.my_table.name = cast(? as varchar))
when matched then update set public.my_table.email = excluded.email
when not matched then insert (token, name, email)
values (cast(? as varchar), cast(? as varchar), cast(? as varchar))
-- [42122-200]
您在 jOOQ 中混合了 3 种方言 API 用法:
SQLDialect.MYSQL
方言,它是产生 onDuplicateKeyUpdate()
语法的方言。这可以在各种方言上模拟,但通常最好使用本地语法(SQL 标准 MERGE
如果可用,或 ON CONFLICT
在 PostgreSQL 中)。
SQLDialect.POSTGRES
方言,这是您的生产目标方言
SQLDialect.H2
方言,用于集成测试
考虑到您可能只将 PostgreSQL 作为生产数据库产品这一事实,这非常复杂。我强烈建议您使用 testcontainers 进行集成测试,这样您就可以从等式中删除 H2。此外,一旦你只将 PostgreSQL 作为目标方言,你就可以避免使用 onDuplicateKeyUpdate()
语法,并使用 jOOQ 的原生 onConflict()
语法支持以获得更可预测的结果。
如果您继续混合使用上述 3 种方言,您通常会 运行 遇到一些限制,即 jOOQ 或 H2 无法模拟您正在使用的语法。如果您实际上必须在生产中支持 3 种方言,这只是一个可以接受的情况。
问题出在 Postgres ON CONFLICT
语法上。
版本(maven 依赖项):
- postgresql: 42.2.9
- jooq: 3.12.3
- h2数据库:1.4.200
// mocking connection
final Connection connection = DriverManager.getConnection("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;Mode=PostgreSQL", "sa", "");
final Settings settings = new Settings().withRenderNameStyle(RenderNameStyle.AS_IS);
Mockito.doReturn(DSL.using(connection, SQLDialect.POSTGRES, settings)).when(mockDbConn).getDSLContext();
// java code for upsert:
dc.insertInto(MY_TABLE)
.columns(MY_TABLE.TOKEN, MY_TABLE.NAME, MY_TABLE.EMAIL)
.values(token, name, email)
.onDuplicateKeyUpdate()
.set(MY_TABLE.EMAIL, email)
.execute();
得到以下错误日志(似乎问题可能是因为[*]
(第4行↓)。我不明白为什么会出现以及如何删除它):
-- Syntax error in SQL statement:
INSERT INTO PUBLIC.MY_TABLE (TOKEN, NAME, EMAIL)
VALUES (?, ?, ?)
ON CONFLICT ([*]TOKEN, NAME) -- line 4
DO UPDATE SET EMAIL = EXCLUDED.EMAIL;
-- expected "DO";
-- SQL statement:
insert into public.my_table (token, name, email)
values (?, ?, ?)
on conflict (token, name)
do update set email = excluded.email;
-- [42001-200]
如果我将方言从 SQLDialect.POSTGRES
切换到 SQLDialect.H2
会发生以下情况:
-- Column "EXCLUDED.EMAIL" not found; SQL statement:
merge into public.my_table using (select 1 one)
on (public.my_table.token = cast(? as varchar) and public.my_table.name = cast(? as varchar))
when matched then update set public.my_table.email = excluded.email
when not matched then insert (token, name, email)
values (cast(? as varchar), cast(? as varchar), cast(? as varchar))
-- [42122-200]
您在 jOOQ 中混合了 3 种方言 API 用法:
SQLDialect.MYSQL
方言,它是产生onDuplicateKeyUpdate()
语法的方言。这可以在各种方言上模拟,但通常最好使用本地语法(SQL 标准MERGE
如果可用,或ON CONFLICT
在 PostgreSQL 中)。SQLDialect.POSTGRES
方言,这是您的生产目标方言SQLDialect.H2
方言,用于集成测试
考虑到您可能只将 PostgreSQL 作为生产数据库产品这一事实,这非常复杂。我强烈建议您使用 testcontainers 进行集成测试,这样您就可以从等式中删除 H2。此外,一旦你只将 PostgreSQL 作为目标方言,你就可以避免使用 onDuplicateKeyUpdate()
语法,并使用 jOOQ 的原生 onConflict()
语法支持以获得更可预测的结果。
如果您继续混合使用上述 3 种方言,您通常会 运行 遇到一些限制,即 jOOQ 或 H2 无法模拟您正在使用的语法。如果您实际上必须在生产中支持 3 种方言,这只是一个可以接受的情况。