【发布时间】:2019-06-19 11:29:38
【问题描述】:
我正在尝试使用 Hibernate 4.3 和 SQL-Server 2014 仅对尚未存储的实体执行批量插入到表中的操作。 我创建了一个简单的表,主键定义为忽略重复键
create table items
(
itemid uniqueidentifier not null,
itemname nvarchar(30) not null,
)
alter table items add constraint items_pk primary key ( itemid ) with ( ignore_dup_key = on );
尝试通过StatelessSession insert方法进行批量插入,如果一个或多个实体已经存入数据库表,批量插入可能会失败:Hibernate throws a StaleStateException:
org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
at org.hibernate.jdbc.Expectations$BasicExpectation.checkBatched(Expectations.java:81)
at org.hibernate.jdbc.Expectations$BasicExpectation.verifyOutcome(Expectations.java:73)
at org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:63)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3124)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3581)
at org.hibernate.internal.StatelessSessionImpl.insert(StatelessSessionImpl.java:144)
at org.hibernate.internal.StatelessSessionImpl.insert(StatelessSessionImpl.java:123)
at it.test.testingestion.HibernateStatelessSessionPersisterImpl.persistData(HibernateStatelessSessionPersisterImpl.java:18)
at it.test.testingestion.App.main(App.java:76)
当批处理语句完成时,由于忽略重复键,Hibernate 会检查返回的行数,该行数与预期不同。
使用 JDBC,使用准备好的语句执行批量插入,已存储到目标表中的实体被跳过,但新实体被正确保存。
如何配置 Hibernate 以执行批量插入而忽略现有数据或不检查受影响的行?
非常感谢
更新 #1
作为解决方法,即使发生重复插入,为了强制受影响的行数,我创建了以下 Hibernate 拦截器:
public class CustomInterceptor extends EmptyInterceptor {
private static final long serialVersionUID = -8022068618978801796L;
private String getTemporaryTableName() {
String currentThreadName = Thread.currentThread().getName();
return "##" + currentThreadName.replaceAll("[^A-Za-z0-9\\_]", "");
}
private void createTemporaryTable(Connection connection) {
String tempTableName = this.getTemporaryTableName();
String commandText = String.format("if (object_id('tempdb.dbo.%s') is null) begin create table [%s] ( dummyfield int ); insert into %s ( dummyfield ) values ( 0 ) end ", tempTableName, tempTableName, tempTableName);
try (PreparedStatement statement = connection.prepareStatement(commandText)) {
statement.execute();
connection.commit();
} catch (SQLException e) {
throw new RuntimeException(String.format("An error has been occurred trying to create the temporary table %s", tempTableName), e);
}
}
public CustomInterceptor(Connection connection) {
this.createTemporaryTable(connection);
}
@Override
public String onPrepareStatement(String sql) {
int ps = sql.toLowerCase().indexOf("insert into ");
if (ps == 0) {
String tableName = this.getTemporaryTableName();
return sql + "; if (@@rowcount = 0) update [" + tableName + "] set dummyfield = 1";
}
return super.onPrepareStatement(sql);
}
}
拦截器在创建新实例时创建一个插入新记录的新临时表。 当插入语句被拦截时,如果插入语句没有影响任何行,则执行保存到实例化临时表中的记录的更新:如果插入重复实体并且没有 StatelessSessionImpl 异常,这会欺骗 Hibernate 关于返回的行事件扔了。
显然,该技巧的缺点是对未插入到表中的每一行执行额外更新的成本。
有谁知道更好的方法,不影响插入性能,将实体插入到使用 Hibernate 忽略重复条目的表中?
谢谢
【问题讨论】:
标签: java sql-server hibernate batch-insert