java - Hibernate+SQLServer / 批量只插入新记录
问题描述
我正在尝试使用 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 抛出 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 忽略重复条目的表中?
谢谢
解决方案
为了获得更好的性能,我更喜欢使用 JDBCBatchUpdate
方法一:
当您过滤新记录时,记录数不会成为约束。因此,您可以在实体层中指定关联映射,并可以执行 Hibernate 批量插入或 JDBC 批量更新。
方法 2:使用原生 SQL 查询
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
//get Connction from Session
session.doWork(new Work() {
@Override
public void execute(Connection conn) throws SQLException {
PreparedStatement pstmt = null;
try{
String sqlInsert = "insert into tbl(name) values (?) ";
pstmt = conn.prepareStatement(sqlInsert );
int i=0;
for(String name : list){
pstmt .setString(1, name);
pstmt .addBatch();
//20 : JDBC batch size
if ( i % 20 == 0 ) {
pstmt .executeBatch();
}
i++;
}
pstmt .executeBatch();
}
finally{
pstmt .close();
}
}
});
tx.commit();
session.close();
推荐阅读
- java - 没有找到适合 parse(java.io.InputStream) 的方法
- delphi - 在运行 DWScript 时监视当前行号?
- javascript - 如何使用 jQuery 弹出模式将数据保存到 SQL 表?
- pandas - 尝试使用 pandas 将 nparray 转换为数据框 - 遇到错误 - 系列的真值是模棱两可的
- swift - 用部分确定枚举大小写
- javascript - Javascript:使用对象作为命名空间,并且在命名空间内构造对象期间无法访问属性
- c# - 使用 lambda 表达式 C# 从表中获取特定行号
- c# - MVC 忽略 web.config 中的文化设置
- javascript - 使用 .css("background-color") 的 jQuery 突出显示文本不起作用
- python - Python - 将 title() 函数应用于列中的值