Hibernate+SQLServer / 批量只插入新记录
Posted
技术标签:
【中文标题】Hibernate+SQLServer / 批量只插入新记录【英文标题】:Hibernate+SQLServer / Batch insert only the new records 【发布时间】: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 忽略重复条目的表中?
谢谢
【问题讨论】:
【参考方案1】:为了更好的性能,我更喜欢使用 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();
【讨论】:
非常感谢您的回答。但是,在大型实体模型场景中,使用 JDBC 方法,意味着对 Hibernate 提供的持久层进行重新编码,包括 listeners、evners 等。出于性能原因是否也可以使用本机 SQL-Bulkcopy API,但问题与使用 JDBC 方法相同。以上是关于Hibernate+SQLServer / 批量只插入新记录的主要内容,如果未能解决你的问题,请参考以下文章