Загрузить отношения «многие ко многим» и сопоставить с сущностью

#java #spring-boot #hibernate #spring-data-jpa #hibernate-envers

#java #весенняя загрузка #спящий режим #spring-data-jpa #спящий режим-envers

Вопрос:

Я довольно новичок в разработке Spring Boot / Hibernate, это был мой первый большой проект.

У меня есть приложение, использующее Spring Boot, Hibernate и Hibernate Envers для аудита некоторых объектов. Для спящего режима Envers настроено использование ValidityAuditStrategy . Поскольку Hibernate Envers пока не поддерживает готовую загрузку отношений «Многие из коробки», я пытаюсь найти способ выполнить один запрос и получить все необходимые мне данные, чтобы избежать проблемы с N 1 запросами, которая на данный момент снижает мою производительность: это занимаетпочти 2 минуты в нашей среде разработки, чтобы полностью загрузить нужную нам сущность с требуемыми отношениями.

Поскольку мне нужно получить проверенную версию, я не могу использовать EntityGraph, как я использовал в другой части приложения (по крайней мере, в моем понимании).

В одной из ситуаций, которую мне нужно решить, есть 4 объекта Parameter , Formula , Value и Variable , которые связаны следующим образом (показаны только соответствующие части, Value и Variable являются объектами и не перечислены здесь)

 @Entity
public class Parameter {
  @OneToOne(fetch = FetchType.LAZY, optional = false)
  @JoinColumn(name = "formula_id", referencedColumnName = "id", nullable = false)
  private Formula formula;
}

@Entity
public class Formula {
  @ManyToOne(optional = false)
  @JoinColumn(name = "tag_id")
  private Value tag;

  @ManyToMany
  @JoinTable(
      name = "Formulas_Variables",
      joinColumns = @JoinColumn(name = "formula_id"),
      inverseJoinColumns = @JoinColumn(name = "variable_id"))
  private Set<Variable> variables = new HashSet<>();
}
  

Что я пытался сделать, так это создать пользовательский запрос и сопоставить результат с помощью @NamedNativeQuery and @SqlResultSetMapping но, несмотря на то, что Hibernate правильно создает связь между Formula и Value , он не создает массив в variables поле. Запрос и сопоставление, которые я использовал, следующие:

 @SqlResultSetMapping(name = "Parameter.findAllByRevisionMapping", entities = {
    @EntityResult(entityClass = Parameter.class, fields = {
        @FieldResult(name = "id", column = "id"),
        @FieldResult(name = "formula", column = "formula_id")
    }),
    @EntityResult(entityClass = Formula.class, fields = {
        @FieldResult(name = "id", column = "formulaId"),
        @FieldResult(name = "tag", column = "tag_id"),
        @FieldResult(name = "variables", column = "variable_id")
    }),
    @EntityResult(entityClass = DomainValue.class, fields = {
        @FieldResult(name = "id", column = "tag_id")
    }),
    @EntityResult(entityClass = Variable.class, fields = {
        @FieldResult(name = "id", column = "variableId")
    })
})
@NamedNativeQuery(name = "Parameter.findAllByRevision", query = "SELECT om_c_p_aud.id,n"
      "       f_aud.id AS formulaId,n"
      "       dv_aud.id AS tag_id,n"
      "       fv_aud.formula_id AS formula_id,n"
      "       v_aud.id AS variable_id,n"
      "       v_aud.id AS variableIdn"
      "FROM Parameters_AUD om_c_p_audn"
      "         LEFT OUTER JOIN Formulas_AUD f_audn"
      "                    ON f_aud.id = om_c_p_aud.formula_id AND f_aud.REV <= ?1 ANDn"
      "                       f_aud.REVTYPE <> 2 AND (f_aud.REVEND > ?1 ORn"
      "                                               f_aud.REVEND IS NULL)n"
      "         LEFT OUTER JOIN Values_AUD dv_audn"
      "                    ON dv_aud.id = f_aud.tag_id AND dv_aud.REV <= ?1 ANDn"
      "                       dv_aud.REVTYPE <> 2 AND (dv_aud.REVEND > ?1 ORn"
      "                                                dv_aud.REVEND IS NULL)n"
      "         LEFT OUTER JOIN Formulas_Variables_AUD fv_audn"
      "                    ON fv_aud.formula_id = f_aud.id AND fv_aud.REV <= ?1 ANDn"
      "                       fv_aud.REVTYPE <> 2 AND (fv_aud.REVEND > ?1 ORn"
      "                                                fv_aud.REVEND IS NULL)n"
      "         LEFT OUTER JOIN Variables_AUD v_audn"
      "                    ON v_aud.id = fv_aud.variable_id AND v_aud.REV <= ?1 ANDn"
      "                       v_aud.REVTYPE <> 2 AND (v_aud.REVEND > ?1 ORn"
      "                                               v_aud.REVEND IS NULL)n"
      "WHERE om_c_p_aud.REV <= ?1n"
      "  AND om_c_p_aud.REVTYPE <> 2n"
      "  AND (om_c_p_aud.REVEND > ?1 ORn"
      "       om_c_p_aud.REVEND IS NULL)", resultSetMapping = "Parameter.findAllByRevisionMapping")
  

Пример результата запроса, выполняемого непосредственно в базе данных, выглядит следующим образом:
Пример запроса результат

Результирующий массив объектов json, который я получаю при вызове findAllByRevision запроса, выглядит примерно так

 [
   {
      "id":1,
      "formula":{
         "id":52,
         "tag":{
            "id":20
         },
         "variables":null
      }
   },
   {
      "id":2,
      "formula":{
         "id":88,
         "tag":{
            "id":24
         },
         "variables":null
      }
   },
   {
      "id":2,
      "formula":{
         "id":88,
         "tag":{
            "id":24
         },
         "variables":null
      }
   },
   {
      "id":2,
      "formula":{
         "id":88,
         "tag":{
            "id":24
         },
         "variables":null
      }
   },
   {
      "id":2,
      "formula":{
         "id":88,
         "tag":{
            "id":24
         },
         "variables":null
      }
   },
   {
      "id":2,
      "formula":{
         "id":88,
         "tag":{
            "id":24
         },
         "variables":null
      }
   },
   {
      "id":2,
      "formula":{
         "id":88,
         "tag":{
            "id":24
         },
         "variables":null
      }
   }
]
  

в то время как я ожидал, что

 [
   {
      "id":1,
      "formula":{
         "id":52,
         "tag":{
            "id":20
         },
         "variables":[
            {
               "id":4
            }
         ]
      }
   },
   {
      "id":2,
      "formula":{
         "id":88,
         "tag":{
            "id":24
         },
         "variables":[
            {
               "id":3
            },
            {
               "id":23
            },
            {
               "id":33
            },
            {
               "id":34
            },
            {
               "id":35
            },
            {
               "id":52
            }
         ]
      }
   }
]
  

У кого-нибудь есть идеи, почему он не создает отношения formulas <-> variables? Я попытался проверить запрос, созданный Hibernate при использовании EntityGraph для аналогичной ситуации, и он показался мне таким же, как показано выше. Однако у меня нет способа проверить отображение, используемое в этом случае (опять же, насколько я понимаю).

Ответ №1:

Вы должны иметь возможность использовать HQL для указания выборки соединений для проверенных ассоциаций. Проверяемый объект похож на обычный объект. Имя объекта обычно имеет суффикс «_AUD», поэтому, если вы хотите запросить информацию об аудите Parameters , вы можете запросить Parameters_AUD , например,:

 SELECT p
FROM Parameters_AUD p
LEFT JOIN FETCH p.values
LEFT JOIN FETCH p.variables
  

Комментарии:

1. Это было бы здорово, но на данный момент у меня нет проверенных объектов класса, и я получаю сообщение об ошибке, если я просто пытаюсь добавить запрос как есть. Должен ли я создавать сущности для всех проверенных классов?

2. Чтобы добавить к этому, если я определяю a Parameter_AUD , я получаю следующую ошибку при запуске приложения: «Дублировать сопоставление объектов com.myPackage.entities. Параметр_ауд`

3. Для этого вам не нужно определять классы. Когда вы активируете Hibernate Envers для такой сущности, объект режима ОТОБРАЖЕНИЯ создается автоматически за кулисами. Имя сущности имеет _AUD суффикс, поэтому вы можете использовать это имя для запросов. Удалите пользовательские классы. Какую ошибку вы получаете?

4. Ошибка, которую я получал, заключалась в том, что Parameter_AUD объект не существовал. Если я создавал объект с таким именем, я получал ошибку дублированной сущности. Итак, через некоторое время я попытался использовать fqn, и это сработало. Несмотря на это, у меня все еще были некоторые проблемы, поскольку столбцы REV, REVEND и REVTYPE были недоступны, как в примере запроса, вероятно, потому, что они вложены в какой-то внутренний объект. Поскольку я изо всех сил пытался заставить его работать таким образом, я решил использовать другой подход и загружать каждый список объектов отдельно и создавать отношения вручную. Мне все же было бы интересно узнать, как сопоставлять сущности

5. Вы правы, имя объекта — это FQN «_AUD», извините, я недостаточно ясно выразился об этом. Вы можете проверить метамодель сущности, чтобы понять, как вы можете получить доступ к этой информации. У каждого объекта envers есть REVTYPE originalId атрибут and . originalId Атрибут имеет встраиваемый тип и имеет вложенные атрибуты id и REV . Проверенные ассоциации сопоставляются с помощью их простых атрибутов столбца объединения, поэтому вам нужно выполнить объединение сущностей, чтобы объединить эту информацию, если она вам нужна.