Как сохранить много объектов (JPA)

Мне нужно обработать файл 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. Оба они занимают примерно такое же количество времени (459s против 443s). Вопрос в том, что сохранение объектов один за другим, как это, является оптимальным. Насколько я знаю, Hibernate (который является моим провайдером JPA) реализует некоторые функции кеша/флеша, поэтому мне не стоит беспокоиться об этом.

Ответ 1

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

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

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

Итак, в 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 изменений в настройках партии.

Ответ 2

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

Отъезд EntityManager.getTransaction

Ответ 3

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

@see EntityManager.createNativeQuery

Ответ 4

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

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

    dao.save(thingy.toModel())

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

}