#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
. Проверенные ассоциации сопоставляются с помощью их простых атрибутов столбца объединения, поэтому вам нужно выполнить объединение сущностей, чтобы объединить эту информацию, если она вам нужна.