执行前获取一行 update/delete
Fetching a row before execute update/delete
是否可以在进行更改之前获取已删除或更新的行?
假设我有以下陈述
dslContext.deleteFrom(Tables.USERS)
.where(Tables.USERS.NAME.eq("xy"))
.execute();
我想打印与 where
语句匹配的所有行。我知道我可以使用 DefaultExecuteListener
class,但我不知道该怎么做。
遗憾的是,MySQL 尚不支持 DELETE .. RETURNING
样式语句(见下文)。
适用于所有具有多次往返的 RDBMS 的解决方案
适用于各种复杂的 DELETE
和 UPDATE
语句的彻底解决方案将使用 VisitListener
将语句转换为等效的 SELECT
语句。如果你想让它总是工作,我可以想到很多必须考虑的边缘情况。
如果“80/20”解决方案足够好,那么您可以使用这个基于正则表达式的简单 ExecuteListener
。这是一个例子:
create table t (i int primary key, j int);
insert into t values (1, 1), (2, 2), (3, 3);
然后:
try (Connection c = getConnection()) {
DSLContext ctx = DSL.using(c);
ctx.configuration().set(new DefaultExecuteListener() {
@Override
public void executeStart(ExecuteContext c) {
if (c.query() instanceof Delete)
System.out.println(ctx.fetch(c.sql().replace("delete from", "select * from")));
}
});
System.out.println(ctx.delete(table("t")).where("i > 1").execute());
}
以上程序的输出为:
+----+----+
| i| j|
+----+----+
| 2| 2|
| 3| 3|
+----+----+
2
这种方法有一些缺点,对您来说可能重要也可能不重要:
- 它不适用于普通 SQL 查询。如果这是一个问题,请将
instanceof Delete
支票替换为更昂贵的 c.sql().startsWith("delete")
支票。
- 它不考虑区分大小写(例如普通 SQL
DELETE
语句)或格式化 SQL。当然,这是可以修复的。
- 它不适用于某些 MySQL 特定的
DELETE
子句,例如 PARTITION
或 IGNORE
,您也可以修复它们。
- 该方法假设可以在同一个
Connection
上 运行 第二个语句。根据您的 DataSource
/ 交易模型,情况可能并非如此。
适用于某些非MySQL RDBMS
的解决方案
为了完整性和此答案的未来读者,我还将提供一个适用于 DB2、Firebird、Oracle、PostgreSQL 和 SQL 服务器的解决方案,它们都具有DELETE .. RETURNING
的一种形式或等效语句。在那些 RDBMS 上,你可以写:
dslContext.deleteFrom(Tables.USERS)
.where(Tables.USERS.NAME.eq("xy"))
.returning()
.fetch();
这将在一条语句中删除记录和 return 所有受影响的记录,而不是创建两次往返。
是否可以在进行更改之前获取已删除或更新的行?
假设我有以下陈述
dslContext.deleteFrom(Tables.USERS)
.where(Tables.USERS.NAME.eq("xy"))
.execute();
我想打印与 where
语句匹配的所有行。我知道我可以使用 DefaultExecuteListener
class,但我不知道该怎么做。
遗憾的是,MySQL 尚不支持 DELETE .. RETURNING
样式语句(见下文)。
适用于所有具有多次往返的 RDBMS 的解决方案
适用于各种复杂的 DELETE
和 UPDATE
语句的彻底解决方案将使用 VisitListener
将语句转换为等效的 SELECT
语句。如果你想让它总是工作,我可以想到很多必须考虑的边缘情况。
如果“80/20”解决方案足够好,那么您可以使用这个基于正则表达式的简单 ExecuteListener
。这是一个例子:
create table t (i int primary key, j int);
insert into t values (1, 1), (2, 2), (3, 3);
然后:
try (Connection c = getConnection()) {
DSLContext ctx = DSL.using(c);
ctx.configuration().set(new DefaultExecuteListener() {
@Override
public void executeStart(ExecuteContext c) {
if (c.query() instanceof Delete)
System.out.println(ctx.fetch(c.sql().replace("delete from", "select * from")));
}
});
System.out.println(ctx.delete(table("t")).where("i > 1").execute());
}
以上程序的输出为:
+----+----+
| i| j|
+----+----+
| 2| 2|
| 3| 3|
+----+----+
2
这种方法有一些缺点,对您来说可能重要也可能不重要:
- 它不适用于普通 SQL 查询。如果这是一个问题,请将
instanceof Delete
支票替换为更昂贵的c.sql().startsWith("delete")
支票。 - 它不考虑区分大小写(例如普通 SQL
DELETE
语句)或格式化 SQL。当然,这是可以修复的。 - 它不适用于某些 MySQL 特定的
DELETE
子句,例如PARTITION
或IGNORE
,您也可以修复它们。 - 该方法假设可以在同一个
Connection
上 运行 第二个语句。根据您的DataSource
/ 交易模型,情况可能并非如此。
适用于某些非MySQL RDBMS
的解决方案为了完整性和此答案的未来读者,我还将提供一个适用于 DB2、Firebird、Oracle、PostgreSQL 和 SQL 服务器的解决方案,它们都具有DELETE .. RETURNING
的一种形式或等效语句。在那些 RDBMS 上,你可以写:
dslContext.deleteFrom(Tables.USERS)
.where(Tables.USERS.NAME.eq("xy"))
.returning()
.fetch();
这将在一条语句中删除记录和 return 所有受影响的记录,而不是创建两次往返。