#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.
Что нужно проверить:
- Убедитесь, что вы используете единственную транзакцию (Да, очевидно, вы уверены в этом)
- Проверьте, использует ли ваш поставщик 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.
Комментарии:
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.