Hibernate + SQLServer / Batch вставляет только новые записи - PullRequest
0 голосов
/ 25 января 2019

Я пытаюсь, используя 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 Ответ

0 голосов
/ 30 января 2019

Для повышения производительности я бы предпочел использовать JDBCBatchUpdate

Подход 1:

Поскольку вы фильтруете новые записи, количество записей не будет ограничением.Таким образом, вы можете указать сопоставление ассоциации на уровне объектов и выполнить пакетную вставку 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();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...