2015-12-14 3 views
1

Существует метод, который возвращает объект из базы данных JPA. У этого объекта есть список для других объектов, тип выборки LAZY. Когда я хочу, чтобы добавить объект к этому списку я получил исключение:JPA lazy fetch list вызывает запросы SELECT в setter

Caused by: Exception [EclipseLink-7242] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.ValidationException 
Exception Description: An attempt was made to traverse a relationship using indirection that had a null Session. This often occurs when an entity with an uninstantiated LAZY relationship is serialized and that lazy relationship is traversed after serialization. To avoid this issue, instantiate the LAZY relationship prior to serialization. 

Так что для того, чтобы преодолеть это я могу инициализировать этот список, выполнив .size() на него. Дело в том, что мне действительно не нужны эти объекты для извлечения из базы данных, поэтому я хотел бы сделать что-то вроде этого: fetchedEntity.setMyLazyFetchList(new ArrayList<>());, который отлично работает. Я могу получить доступ к моему списку, но проблема следующая: set method вызывает те же запросы на выбор, что и fetchedEntity.getMyLazyFetchList().size(). Эти запросы бесполезны, поскольку я устанавливаю значение для нового списка, поэтому почему они вызываются?

Метод выборка лицо

public Competitor findAndInitializeEmptyGroups(Integer idCompetitor) { 
    Competitor entity = em.find(Competitor.class, idCompetitor); 
    System.out.println("Before set "); 
    entity.setGroupCompetitorList(new ArrayList<>()); 
    System.out.print("After set lazy list size "); 
    System.out.print(entity.getGroupCompetitorList().size()); 

    return entity; 
} 

Ленивый выборки список полей в сущности (Конкурент)

@OneToMany(mappedBy = "idCompetitor") 
private List<GroupCompetitor> groupCompetitorList = new ArrayList<>(); 

Второе поле конечного отношения (GroupCompetitor)

@JoinColumn(name = "id_competitor", referencedColumnName = "id_competitor") 
@ManyToOne(optional = false) 
private Competitor idCompetitor; 

Что журналы говорят:

Info: Before set 
Fine: SELECT id_group_competitor, id_competitor, id_group_details FROM group_competitor WHERE (id_competitor = ?) 
bind => [43] 
Fine: SELECT id_group_details, end_date, start_date, version, id_competition, id_group_name FROM group_details WHERE (id_group_details = ?) 
bind => [241] 
... 
many more SELECTs 

Info: After set lazy list size 
Info: 0 

После замены линии

entity.setGroupCompetitorList(new ArrayList<>()); 

с

entity.getGroupCompetitorList().size(); 

И бревен (они же, кроме списка теперь состоит из принесенных лиц) :

Info: Before set 
Fine: SELECT id_group_competitor, id_competitor, id_group_details FROM group_competitor WHERE (id_competitor = ?) 
bind => [43] 
Fine: SELECT id_group_details, end_date, start_date, version, id_competition, id_group_name FROM group_details WHERE (id_group_details = ?) 
bind => [241] 
... 
many more SELECTs 

Info: After set lazy list size 
Info: 44 

Так что мой вопрос: почему SELECT запросы вызываются, когда я делаю entity.setGroupCompetitorList(new ArrayList<>());? Я не хочу, чтобы они были по соображениям производительности. Есть ли способ устранить эту проблему или что именно вызывает это поведение?

Использование:

  • EclipseLink JPA 2.1
  • GlassFish 4.1
  • Java 8
+1

Вы изменяете/получаете доступ к списку, который заставляет запрос в JPA отслеживать любые изменения, внесенные вами в объект. Eclipselink использует ткачество, которое помогает отслеживать изменения, и поэтому, если вы хотите обойти инициирование отношения, вам может понадобиться обойти его плетение. см. http://www.eclipse.org/eclipselink/documentation/2.5/concepts/app_dev007.htm и https://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Advanced_JPA_Development/Performance/Weaving/Disabling_Weaving_with_Persistence_Unit_Properties – Chris

+0

Установка '< свойство name = "eclipselink.weaving.changetracking" value = "false" /> 'или используя' @ChangeTracking (DEFERRED) 'в сущности, вызвало, что эти запросы SELECT не вызывались во время' set() ', но я заметил, что они вызываются во время следующего EntityManager 'flush()', поэтому производительность не улучшилась, но спасибо за ссылки, они помогли мне понять некоторые вещи. – Geinmachi

+0

Вы пытаетесь заменить список? Поставщик JPA нуждается в этом списке, чтобы узнать, какие ссылки удалить в базе данных при слиянии с ним. Если это так, вы хотите использовать функцию ткачества и отслеживания изменений. EclipseLink может отслеживать дополнения к спискам без необходимости извлекать полный список, если отслеживание изменений включено, если объект привязан к контексту. Если вам нужно сериализовать объект, его нужно будет предварительно запрограммировать или вы не сможете получить доступ к коллекции. – Chris

ответ

0

Вы не можете не получать список, который является членом объекта, если вы хотите добавьте элемент, и поставщик JPA сохранит его. Поставщик JPA должен отслеживать владельца, владельца и обрабатывать любые каскадные (которые я не вижу, что вы определили, но я сомневаюсь, что для каждой комбинации каскадных параметров есть другой путь кода). Самый простой способ - иметь список в памяти, а затем решить, какую операцию выполнить в БД при фиксации/сбросе времени.

Я считаю, что причиной вашего первоначального исключения в отношении LAZY является доступ к внешней стороне в управляемом контексте. Когда вы вернетесь из метода EJB, объект, который вы возвращаете, будет отсоединен.Вы должны повторно подключить его к другому EntityManager или убедитесь, что все ленивые отношения, которые вы собираетесь использовать, были загружены до того, как вы покинете этот метод. Вызов fetchedEntity.getMyLazyFetchList().size() был бы примером этого и отлично работает в одном случае. Если вы хотите принудительно загрузить LAZY в список объектов, я предлагаю вам ознакомиться с предложениями LEFT JOIN FETCH. Я предполагаю, что ваш метод findAndInitializeEmptyGroups() находится в EJB, судя по тому, что мне нравится, как вложенный EntitManager em в этом методе, и что методы получат лечение по умолчанию @TransactionAttribute(REQUIRED), так как я не вижу никаких комментариев об обратном.

Теперь давайте вернемся к исходной задаче:

Я хочу, чтобы добавить объект к этому списку

Проблема вы пытаетесь решить, чтобы добавить элемент в список без получения всего списка. Вы используете атрибут mappedBy, то есть вы создали двунаправленную связь. Если getGroupCompetitorList() возвращает неупорядоченный список («сумка» в Hibernate говорит), то вам не нужно загружать список вообще. Попробуйте что-нибудь вроде этого:

Изменить GroupCompetitorInteger idCompetitor@ManyToOne(fetch=FetchType.LAZY) Competitor competitor. Отрегулируйте геттеры и сеттеры соответственно.

CompetitorgroupCompetitorListmappedBy до competitor. Добавьте методы get/set.

Затем вы можете добавить в список со стороны ребенка с помощью метода, как это в EJB:

public void addNewGroupCompetitorToCompetitor(Competitor comp, GroupCompetitor gComp) { 
    gComp.setCompetitor(comp); 
    em.persist(gComp); 
    em.flush(); 
} 

В следующий раз, когда вы принести Competitor снова его и пройти entity.getGroupCompetitorList() (в то время как управляется EntityManager) он должен иметь новый GroupCompetitor, который вы добавили. Этот вид вещей усложняется в зависимости от того, является ли comp новым объектом, который не был сохранен, но это основная идея. Возможно, потребуется некоторая настройка для правильной работы с EclipseLink, но я делаю то же самое с Hibernate как поставщик JPA, и он работает.

+0

Я считаю, что у меня уже есть ваше предлагаемое решение, но мои имена полей вводят в заблуждение (idCompetitor - это тип конкурента, а не целое - автоматически сгенерированный, не изменился, добавив это поле к моему сообщению). Я хотел бы иметь возможность добавлять новые объекты в список без необходимости выбора старых/вызывающих запросов выбора базы данных. На самом деле мне не приходилось добавлять эти объекты в список, потому что я сохраняю их самостоятельно, без каскадирования, и мне нужен только список, чтобы читать его, а не добавлять новые значения. – Geinmachi

+0

Хорошо. Я рад, что вы нашли решение. Для справки, однако, есть моменты, когда вам может понадобиться добавить его в список. Если и родительский, и дочерний (Competitor и GroupCompetitor в этом случае) находятся в памяти и управляются, очистка нового ребенка не будет обновлять список родителей. У родителя будет только правильный список, если он будет загружен после завершения транзакции, которая добавила ребенка. Когда оба находятся в памяти, что я обычно делаю, так как редко добавляю элемент в список, мои операции выглядят так: parent.getChildren(). Add (child); child.setParent (родительский); em.flush(); –

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