JPA вставляет медленно с графом объектов - PullRequest
4 голосов
/ 24 июня 2010

Я пытаюсь сделать каскадное сохранение на большом объектном графе, используя 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 ]

2 голосов
/ 05 июля 2010

Любопытно, почему вы считаете увеличение INCREMENT BY грязным?Это оптимизация, которая уменьшает количество обращений к базе данных для получения следующего значения последовательности и является общим шаблоном, используемым в клиентах базы данных, где значение идентификатора назначается в клиенте до INSERT.Я не рассматриваю это как проблему JPA или ORM, и она должна быть такой же стоимости при сравнении JDBC, поскольку она также должна получить новый порядковый номер для каждой новой строки до INSERT.Если у вас есть другой подход в вашем случае JDBC, тогда мы сможем заставить EclipseLink JPA следовать тому же подходу.

Стоимость JPA, вероятно, наиболее очевидна в изолированном сценарии INSERT, потому что вы не получаете никакогополучайте выгоду от повторных чтений в транзакционном или разделяемом кеше, и в зависимости от конфигурации кеша вы платите цену, чтобы поместить эти новые сущности в кеш в процессе сброса / фиксации.

Обратите внимание, что также существует стоимостьсоздание первого EntityManager, где вся обработка метаданных, загрузка классов, возможно ткачество и инициализация метамодели.Убедитесь, что вы не учитываете это время.В вашем реальном приложении это происходит один раз, и все последующие EntityManager извлекают выгоду из общих метаданных.

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

Надеюсь, это поможет.Мы рады предоставить вам дополнительные рекомендации и информацию о EclipseLink JPA, а также о возможностях производительности и масштабируемости.

Doug

2 голосов
/ 24 июня 2010

Решение состоит в том, чтобы включить пакетную обработку JDBC, а также очищать и очищать EntityManager через равные промежутки времени (такие же, как размер пакета), но я не знаю, как это сделать независимому поставщику:

1 голос
/ 25 июня 2010

Спасибо Паскалю за ответ. Я провел несколько тестов и смог значительно повысить производительность.

Без оптимизации я вставил примерно 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 - довольно высокая цена за простоту кодирования.

...