JPA вставляет медленно с графом объектов
Я пытаюсь сделать каскадное сохранение на большом объектном графе, используя JPA. Например (мой граф объектов немного больше, но достаточно близко):
@Entity
@Table(name="a")
public class A {
private long id;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "a")
private Collection<B> bs;
}
@Entity
@Table(name="b")
public class B {
private long id;
@ManyToOne
private A a;
}
Поэтому я пытаюсь сохранить A, у которого есть коллекция из 100+ B. Код просто
em.persist(a);
Проблема в том, что это МЕДЛЕННО. Мое сохранение занимает около 1300 мс. Я посмотрел на генерируемый SQL, и он ужасно неэффективен. Что-то вроде этого:
select a_seq.nextval from dual;
select b_seq.nextval from dual;
select b_seq.nextval from dual;
select b_seq.nextval from dual;
...
insert into a (id) values (1);
insert into b (id, fk) values (1, 1);
insert into b (id, fk) values (2, 1);
insert into b (id, fk) values (3, 1);
...
В настоящее время использую toplink в качестве поставщика сохраняемости, но я также попробовал eclipselink и hibernate. Бэкэнд это оракул 11г. Проблема действительно в том, как соединить sql. Каждая из этих операций выполняется дискретно, а не навалом, поэтому если между моим appserver и сервером БД задержка в сети составляет даже 5 мс, выполнение 200 дискретных операций добавляет 1 секунду. Я попытался увеличить размер размещения моих последовательностей, но это только немного помогает. Я также попробовал прямой JDBC как пакетное утверждение:
for...{
statement = connection.prepareStatement(sql);
statement.addBatch();
}
statement.executeBatch();
Для моей модели данных требуется около 33 мсек, что делается как прямая партия JDBC. Сам Oracle берет 5 мс для 100+ вставок.
Есть ли способ заставить JPA (я застрял с 1.0 прямо сейчас...) идти быстрее, не вдаваясь в специфические вещи вендора, такие как массовая вставка в спящий режим?
Спасибо!
3 ответа
Решение состоит в том, чтобы включить пакетную обработку JDBC, а также очищать и очищать EntityManager через равные промежутки времени (такие же, как размер пакета), но я не знаю, как это сделать независимым от поставщика способом:
С Hibernate, вам придется установить
hibernate.jdbc.batch_size
Вариант конфигурации. Смотрите Главу 13. Пакетная обработкаС EclipseLink похоже, что существует режим пакетной записи. Посмотрите сообщение Джеффа Сазерленда в этой теме (также должно быть возможно указать размер).
Согласно комментариям к этому сообщению, пакетная запись недоступна в TopLink Essentials:(
Любопытно, почему вы считаете увеличение INCREMENT BY грязным? Это оптимизация, которая уменьшает количество обращений к базе данных для получения следующего значения последовательности и является общим шаблоном, используемым в клиентах базы данных, где значение идентификатора назначается в клиенте до INSERT. Я не рассматриваю это как проблему JPA или ORM, и она должна быть такой же стоимости при сравнении JDBC, поскольку она также должна получать новый порядковый номер для каждой новой строки до INSERT. Если у вас есть другой подход в вашем случае JDBC, тогда мы сможем заставить EclipseLink JPA следовать тому же подходу.
Стоимость JPA, вероятно, наиболее очевидна в изолированном сценарии INSERT, потому что вы не получаете никакой выгоды от повторных чтений в транзакционном или разделяемом кеше, и в зависимости от конфигурации вашего кеша вы платите цену, чтобы поместить эти новые сущности в кеш внутри флеш / фиксации.
Обратите внимание, что существует также стоимость создания первого EntityManager, в котором вся обработка метаданных, загрузка классов, возможно ткачество и инициализация метамодели. Убедитесь, что вы не учитываете это время. В вашем реальном приложении это происходит один раз, и все последующие EntityManager получают выгоду от общих метаданных.
Если у вас есть другие сценарии, для которых необходимо прочитать эти объекты, то стоимость помещения их в кеш может снизить стоимость их извлечения. Исходя из моего опыта, я могу сделать приложение в целом намного быстрее, чем обычное рукописное решение JDBC, но оно сбалансировано для всего набора одновременно работающих пользователей, а не для изолированного тестового примера.
Надеюсь, это поможет. Мы рады предоставить вам дополнительные рекомендации и EclipseLink JPA, а также параметры производительности и масштабируемости.
Doug
Спасибо Паскалю за ответ. Я провел несколько тестов и смог значительно повысить производительность.
Без оптимизации у меня была вставка, занимающая приблизительно 1100 мс. Используя eclipselink, я добавил в файл persistence.xml:
<property name="eclipselink.jdbc.batch-writing" value="JDBC"/>
<property name="eclipselink.jdbc.batch-writing.size" value="1000"/>
Я пробовал другие свойства (Oracle-JDBC и т. Д.), Но JDBC, по-видимому, показал лучшее увеличение производительности. Это привело к уменьшению вставки примерно до 900 мс. Так что довольно скромное увеличение производительности на 200мс. Большая экономия была получена за счет увеличения последовательности sequenceSize. Я не большой поклонник этого. Я считаю грязным увеличивать INCREMENT BY моих последовательностей только для размещения JPA. Увеличение их привело к уменьшению времени примерно до 600 мс для каждой вставки. Таким образом, с этими усовершенствованиями было сброшено в общей сложности около 500 мс.
Все это хорошо, но все же значительно медленнее, чем пакет JDBC. JPA - довольно высокая цена за простоту кодирования.