2016-02-29 5 views
1

Этот сценарий использует простые отношения oneToMany с каскадом в обоих направлениях.JPA сохраняется медленнее и медленнее

Много:

@javax.persistence.Entity(name="Many") 
public class Many { 
    @javax.persistence.ManyToOne(cascade = CascadeType.PERSIST) 
    protected One one; 

    @Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    private long primaryKey; 

    public void setM(One one) { 
     this.one = one; 
     // comment out this line and performance becomes stable 
     this.one.getMany().add(this); 
    } 

    // other setters, getters, etc... 
} 

Один:

@javax.persistence.Entity(name="One") 
public class One { 
    @javax.persistence.OneToMany(mappedBy="m", cascade = CascadeType.PERSIST) 
    protected java.util.Set<Many> many = com.google.common.collect.Sets.newHashSet(); 

    @Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    private long primaryKey; 

    private String name; 

    // setters, getters, etc... 
} 

Тест:

public static void main(String[] args) { 
    while(true) { 
     EntityManagerFactory emf = Persistence.createEntityManagerFactory("test-pu"); 
     EntityManager em = emf.createEntityManager(); 

     for (int i = 0; i < 100; i++) { 
      sw.reset(); 
      sw.start(); 
      persistMVs(emf, em); 
      System.err.println("Elapsed: " + sw.elapsed(TimeUnit.MILLISECONDS) + " ms"); 
     } 

     em.close(); 
     emf.close(); 
    } 
} 

private static void persistMVs(EntityManagerFactory emf, EntityManager em) { 
    em.getTransaction().begin(); 
    One one = getOrCreateOne(em); 

    for (int i = 0; i < 200; i++) { 
     Many many = new Many(); 
     many.setM(one); 
     em.persist(many); 
    } 
    em.getTransaction().commit(); 
} 

Испытание представляет собой бесконечную петлю, которая пытается вставить 20000 Many объектов, связанных с одним объектом One. Каждый цикл начинается с создания нового EntityManagerFactory, чтобы показать отрицательный эффект производительности увеличивающейся базы данных.

Ожидаемое поведение будет заключаться в том, что время вставки объектов не резко возрастает, однако после каждого ВО ВРЕМЯ ЦИКЛА наблюдается увеличение величины порядка.

Примечания:

  • Я попытался EclipseLink, Hibernate, OpenJPA и все страдали от такого спада.
  • Если я не обновляю многие коллекции One, тогда нет деградации (см. Прокомментированную строку Many).
  • Если я не создаю новый EntityManagerFactory, тогда нет деградации даже после полумиллиона объектов.
  • Медленная часть em.persist(many); (я ее измерил).
  • Отъезд https://github.com/kupsef/OneToMany и начать тест с gradle start.

Зачем нужен начальный размер базы данных в этом случае? Должен ли я рассматривать это поведение как ошибку?

+0

Почему вы не смотрите на журнал и не понимаете его? –

+0

Какие журналы вы бы предложили? Журналы sql отличаются только в первом цикле (внутреннего для), он дополнительно содержит выборку из многих объектов. Это не объясняет деградацию, поскольку последующие циклы не извлекают их (скорее всего, потому, что они были кэшированы для последующего использования, как и ожидалось). – kupsef

+0

журналы используемой вами функции JPA. Реализация, которую я использую (DataNucleus), всегда показывает много информации для отслеживания потенциальных проблем, поэтому я предполагаю, что другие одинаково полезны. –

ответ

3

Просто для того, чтобы расширить ответ Predrag - пересечение отношений 1: M не только имеет стоимость приведения объектов, но и любое расширение объекта-объекта, но эти объекты остаются управляемыми в постоянном блоке. Поскольку ваш тест повторно использует один и тот же EntityManager для повторных транзакций, кеш управляемых объектов продолжает расти с каждой итерацией. Этот кеш управляемых объектов должен быть пройден и проверен на изменения каждый раз, когда контекст синхронизируется с базой данных - это происходит при сбое, транзакции или даже в запросах.

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

Редактировать> Ваш класс «Многие» переопределил метод hashCode и строит свой хэш-код, используя хэш-код его ссылки «Один» с его первичным ключом. Это приводит к тому, что каждый «много» вы сохраняете в своих циклах один и тот же хэш-код, поскольку GenerationType.IDENTITY может назначать последовательности только при выполнении инструкции insert, что происходит во время синхронизации (flush/commit). Этот метод может вызывать поиск в кеше, который возникает, когда поставщик обходит растущую объектную модель при каждом вызове persist из-за вызова каскада persist, чтобы занимать больше времени и дольше.

+0

Размер кеша растет, я знаю это, но без воссоздания EMF нет ухудшения производительности и даже в этом случае все сущности будут управляться и присутствовать в кеше. Также обратите внимание, что внутреннее для циклов принимает одно и то же время внутри цикла while, но резко возрастает после каждого итерации. – kupsef

+0

Попробуйте удалить параметры сохранения каскада в ваших отношениях, чтобы persist не приходилось каскадировать через растущую объектную модель, но я не уверен, что понял ваше утверждение. EM имеет свой собственный кеш, который растет, и каждый объект, который вы передаете в persist, должен быть проверен на него. Вы также переопределили equals и hashcode - попробуйте удалить вашу реализацию и посмотреть, влияет ли это на ваши результаты. – Chris

+0

Действительно, проблема была в equals/hashCode, но не так, как вы ожидали :) Без них время вставки увеличивается после каждого внутреннего цикла (который был постоянным в течение цикла while). Таким образом, плохо реализованный equals/hashCode каким-то образом скрыл временную деградацию. – kupsef

2

Я думаю, проблема в this.one.getMany(), потому что на каждой итерации все больше и больше объектов нужно загружать из этой связи.

@OneToMany отношение по умолчанию является ленивым, поэтому при вызове getMany() Поставщик JPA должен инициализировать каждую сущность формы коллекции, которая занимает больше времени по мере роста размера.

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

+0

Тогда я ожидал бы одну задержку при первом вызове getMany(), чтобы заполнить кеш, но он остается медленным через все сущности (все 20000 Many). – kupsef

+0

Я проверил его, и только первый вызов getMany() извлекает записи:/ – kupsef

Смежные вопросы