Как сохранить множество сущностей (JPA)

#java #jpa #entitymanager

#java #jpa #entitymanager

Вопрос:

Мне нужно обработать CSV-файл и для каждой записи (строки) сохранить объект. Прямо сейчас я делаю это следующим образом:

 while ((line = reader.readNext()) != null) {
    Entity entity = createEntityObject(line);
    entityManager.save(entity);
    i  ;
}
  

где save(Entity) метод — это, по сути, просто EntityManager.merge() вызов. В файле CSV содержится около 20 000 объектов (строк). Является ли это эффективным способом сделать это? Кажется, что это довольно медленно. Было бы лучше использовать EntityManager.persist() ? Является ли это решение каким-либо недостатком?

Редактировать

Это длительный процесс (более 400 секунд), и я попробовал оба решения, с persist и merge . Для завершения обоих требуется примерно одинаковое количество времени (459 секунд против 443 секунд). Вопрос в том, оптимально ли сохранять объекты один за другим, как это. Насколько я знаю, Hibernate (который является моим поставщиком JPA) реализует некоторые функции кэширования / сброса, поэтому мне не стоит беспокоиться об этом.

Ответ №1:

JPA API не предоставляет вам всех возможностей для того, чтобы сделать это оптимальным. В зависимости от того, как быстро вы хотите это сделать, вам придется искать конкретные параметры ORM — в вашем случае Hibernate.

Что нужно проверить:

  1. Убедитесь, что вы используете единственную транзакцию (Да, очевидно, вы уверены в этом)
  2. Проверьте, использует ли ваш поставщик JPA (Hibernate) пакетный API JDBC (см.: hibernate.jdbc.batch_size)
  3. Проверьте, можете ли вы обойти получение сгенерированных ключей (зависит от драйвера db / jdbc, какую выгоду вы получите от этого — см.: hibernate.jdbc.use_getGeneratedKeys)
  4. Проверьте, можете ли вы обойти каскадную логику (от этого выигрывает только минимальная производительность)

Итак, в Ebean ORM это было бы:

     EbeanServer server = Ebean.getServer(null);

    Transaction transaction = server.beginTransaction();
    try {
        // Use JDBC batch API with a batch size of 100
        transaction.setBatchSize(100);
        // Don't bother getting generated keys
        transaction.setBatchGetGeneratedKeys(false);
        // Skip cascading persist 
        transaction.setPersistCascade(false);

        // persist your beans ...
        Iterator<YourEntity> it = null; // obviously should not be null 
        while (it.hasNext()) {
            YourEntity yourEntity = it.next();
            server.save(yourEntity);
        }

        transaction.commit();
    } finally {
        transaction.end();
    }
  

О, и если вы делаете это через raw JDBC, вы пропускаете накладные расходы ORM (меньше создания объектов / сбора мусора и т.д.), Поэтому я бы не стал игнорировать этот вариант.

Итак, да, это не отвечает на ваш вопрос, но может помочь вам в поиске дополнительных настроек для пакетной вставки, специфичных для ORM.

Комментарии:

1. Вы могли бы проверить hibernate.jdbc.batch_size и hibernate.jdbc.use_getGeneratedKeys (но не настраиваемые для каждой транзакции).

Ответ №2:

Я думаю, что один из распространенных способов сделать это — с помощью транзакций. Если вы начинаете новую транзакцию, а затем сохраняете большое количество объектов, они фактически не будут вставлены в базу данных, пока вы не зафиксируете транзакцию. Это может повысить вашу эффективность, если у вас есть большое количество элементов для фиксации.

Ознакомьтесь с EntityManager.getTransaction

Комментарии:

1. Он выполняется в транзакции (с использованием Spring @Transactional).

2. Вы могли бы попробовать удалить аннотацию и посмотреть, изменится ли производительность. Вы также можете подтвердить, что он использует один махом, установив точку останова и после выполнения некоторого количества вызовов perist проверьте базу данных, чтобы подтвердить, что строки еще не вставлены. Может случиться так, что spring фиксируется после 10 или 100 или около того вызовов, и вы можете внести некоторые изменения, чтобы изменить производительность.

Ответ №3:

Чтобы ускорить работу, по крайней мере, в режиме гибернации, вы должны выполнить flush() и clear() после определенного количества вставок. Я применил этот подход к миллионам записей, и он работает. Это все еще медленно, но это намного быстрее, чем не делать этого. Базовая структура выглядит следующим образом:

 int i = 0;
for(MyThingy thingy : lotsOfThingies) {

    dao.save(thingy.toModel())

    if(  i % 20 == 0) {
        dao.flushAndClear();
    }

}
  

Ответ №4:

Вы можете записать их с помощью классического оператора SQL Insert непосредственно в базу данных.

@смотрите EntityManager.createNativeQuery

Комментарии:

1. В этом конкретном случае собственные запросы не обеспечат значительного ускорения. Все, что вы можете сделать, это просто сгруппировать их с помощью пакетной обработки, которую вы можете выполнить на уровне поставщика JPA или драйвера JDBC. Однако в моем конкретном случае я могу использовать INSERT INTO … ВЫБЕРИТЕ ИЗ … комбо, которое значительно ускорит работу, поэтому имейте мой 1.