我要求在插入中忽略来自SQL Server的主键冲突。虽然有几种方法可以完成这项任务,但其中许多方法具有性能影响或不可行。我需要批量插入表中,并且有可能存在重复。
为了完成这项任务,我编写了一个拦截onPrepareStatement
的Hibernate拦截器,并在一个t-SQL TRY CATCH结构中包装了Hibernate生成的SQL。 TRY CATCH查找主键违规并使其静音。下面是我的拦截器代码。
private static final String FORMAT_TRY_CATCH = "BEGIN TRY %s END TRY\r\n" + "BEGIN CATCH\r\n" + "\r\n"
+ "DECLARE @ErrorMessage NVARCHAR(4000),\r\n" + " @ErrorNumber INT,\r\n" + " @ErrorSeverity INT,\r\n" + " @ErrorState INT,\r\n"
+ " @ErrorLine INT,\r\n" + " @ErrorProcedure NVARCHAR(200) ;\r\n" + "\r\n"
+ "SELECT @ErrorNumber = ERROR_NUMBER(), @ErrorSeverity = ERROR_SEVERITY(),\r\n"
+ " @ErrorState = ERROR_STATE(), @ErrorLine = ERROR_LINE(),\r\n"
+ " @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-') ;\r\n" + "\r\n"
+ "SELECT @ErrorMessage = N'Error %%d, Level %%d, State %%d, Procedure %%s, Line %%d, ' +\r\n"
+ " 'Message: ' + ERROR_MESSAGE() ;\r\n" + " IF @ErrorNumber <> 2627\r\n" + " BEGIN\r\n"
+ "RAISERROR (@ErrorMessage, @ErrorSeverity, 1, @ErrorNumber, -- parameter: original error number.\r\n"
+ " @ErrorSeverity, -- parameter: original error severity.\r\n" + " @ErrorState, -- parameter: original error state.\r\n"
+ " @ErrorProcedure, -- parameter: original error procedure name.\r\n"
+ " @ErrorLine-- parameter: original error line number.\r\n" + " ) ;\r\n" + " END\r\n" + "END CATCH;";
@Override
public String onPrepareStatement(String sql) {
if (sql.toLowerCase().startsWith("insert")) {
return String.format(FORMAT_TRY_CATCH, sql);
}
return sql;
}
拦截器的工作原理除外,当存在主键约束违规时,该语句返回0个受影响的行。如果受影响的行数与预期的行数不匹配,Hibernate会有一个抛出异常的检查。我可以通过执行返回一个受影响的行的虚拟t-SQL语句来解决此问题。
DECLARE @dummy TABLE (col1 int)
INSERT INTO @dummy values (1)
当存在主键约束违规时,此伪逻辑将降低性能。是否有更好的虚拟脚本,或更好的方法来捕获主键约束违规?
注意:出于性能原因,在每次插入之前执行选择是不可接受的。我无法重新创建SQL的主键以打开IGNORE_DUP_KEY。