Hibernate+SQLServer / Batch вставляет только новые записи
Я пытаюсь, используя 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 пакетная вставка может завершиться неудачей, если в таблицу базы данных уже сохранены один или несколько объектов: 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);
}
}
Перехватчик, когда создается новый экземпляр, создает новую временную таблицу, вставляя новую запись. Когда оператор вставки перехватывается, выполняется обновление записи, сохраненной во временной таблице экземпляра, если инструкция вставки не затронула ни одной строки: это обманывает Hibernate в отношении события возвращаемых строк, если вставлена дублирующаяся сущность и исключение StatelessSessionImpl не существует выброшены.
Очевидно, что обратной стороной хитрости является стоимость выполнения дополнительного обновления для каждой строки, которая не вставлена в таблицу.
Кто-нибудь знает лучший способ, который не влияет на производительность вставки, чтобы вставить сущности в таблицу, которая игнорирует повторяющиеся записи, используя Hibernate?
Спасибо
1 ответ
Для лучшей производительности я бы предпочел использовать JDBCBatchUpdate
Подход 1:
Поскольку вы фильтруете новые записи, количество записей не будет ограничением. Таким образом, вы можете указать сопоставление ассоциации на уровне объектов и выполнить пакетную вставку Hibernate или пакетное обновление JDBC.
Подход 2. Использование Native SQl Query
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();