У меня есть три объекта: интервью, вопрос и ответ. Интервью может иметь много Вопросов (и наоборот, многие-ко-многим), Вопрос может иметь много Ответов (один-ко-многим).
Я сохраняю и извлекаю сущности следующим образом:
@ApplicationScoped
internal class InterviewRepository : PanacheRepository<Interview> {
fun persistInterview(interview: Interview): Uni<Interview> {
return Panache.withTransaction {
persist(interview)
}
}
fun getInterview(interviewId: Long): Uni<Interview> {
return findById(interviewId)
}
}
// same repos for Question and Answer
Все операции сохранения работают нормально, интервью и вопросы создаются нормально, а затем и то, и другое нормально извлекается. Но когда я создаю ответ (тоже хорошо), а затем пытаюсь найти объекты вопроса или интервью findById, я получаю следующую ошибку (это для получения вопроса):
"org.hibernate.HibernateException: java.util.concurrent.CompletionException: org.hibernate.LazyInitializationException: HR000056: Collection cannot be initialized: com.my.company.question.Question.answers - Fetch the collection using 'Mutiny.fetch', 'Stage.fetch', or 'fetch join' in HQL"
Раньше показывалась та же ошибка для findById (интервью), но FetchMode.JOIN решил проблему. По какой-то причине FetchMode.JOIN игнорируется при получении ответа (?). Вместо использования findById я также попытался вручную написать HQL, используя левое соединение, но получил тот же результат. Что мне здесь не хватает?
Объект интервью:
@Entity(name = "interview")
@RegisterForReflection
internal data class Interview (
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
val interviewId: Long? = null,
@Column(name = "client_id", nullable = false)
val clientId: Long = Long.MIN_VALUE,
@ManyToMany(mappedBy = "interviews", fetch = FetchType.LAZY)
@Fetch(FetchMode.JOIN)
val questions: MutableSet<Question> = mutableSetOf(),
)
Сущность вопроса:
@Entity(name = "question")
@RegisterForReflection
internal data class Question (
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
val questionId: Long? = null,
@Column(name = "client_id", nullable = true)
val clientId: Long? = null,
@OneToMany(mappedBy = "question", fetch = FetchType.LAZY)
@OnDelete(action = CASCADE)
@Fetch(FetchMode.JOIN)
val answers: MutableSet<Answer> = mutableSetOf(),
@ManyToMany(cascade = [CascadeType.ALL], fetch = FetchType.LAZY)
@JoinTable(
name = INTERVIEW_QUESTION_TABLE,
joinColumns = [JoinColumn(name = "question_id", referencedColumnName = "id")],
inverseJoinColumns = [JoinColumn(name = "interview_id", referencedColumnName = "id")]
)
@Fetch(FetchMode.JOIN)
@JsonIgnore
val interviews: MutableList<Interview> = mutableListOf(),
)
Ответить Сущность:
@Entity(name = "answer")
@RegisterForReflection
internal data class Answer (
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
val answerId: Long? = null,
@Column(name = "question_id", nullable = false)
val questionId: Long = Long.MIN_VALUE,
@ManyToOne(fetch = FetchType.LAZY)
@Fetch(FetchMode.JOIN)
@JoinColumn(
name = "question_id",
foreignKey = ForeignKey(ConstraintMode.NO_CONSTRAINT),
nullable = false,
insertable = false,
updatable = false,
)
@JsonIgnore
val question: Question = Question(),
)
В Hibernate Reactive ленивые коллекции должны извлекаться явно. Если вы попытаетесь получить доступ к ленивой ассоциации, не извлекая ее сначала, вы увидите эту ошибку.
Вот как вы можете получить ассоциацию с Panache:
import org.hibernate.reactive.mutiny.Mutiny;
fun getInterview(interviewId: Long): Uni<Interview> {
return findById(interviewId)
.call(interview -> Mutiny.fetch(interview.questions))
}
Обратите внимание, что получение ассоциаций таким образом вызовет дополнительный запрос к базе данных. В зависимости от варианта использования может быть эффективнее загружать ассоциацию сразу с выборкой соединения в запросе. Это также должно работать:
fun getInterview(interviewId: Long): Uni<Interview> {
return find("from Interview i left join fetch i.questions where i.id = :id", Parameters.with("id", interviewId))
}
В этом случае Hibernate Reactive загрузит все одним запросом.
Кроме того, при использовании Hibernate разработчик несет ответственность за синхронизацию двунаправленных ассоциаций с обеих сторон.
Спасибо за репродуктор. Это действительно полезно. Но можете ли вы создать тестовый пример, который я могу запустить, пожалуйста? Я не знаком с почтальоном, и мне потребуется некоторое время, чтобы понять, как воспроизвести ошибку. Последовательность запросов curl к остальным API тоже будет хорошей.
Спасибо за проверку! Я только что обновил репо с созданным тестовым примером.
Спасибо, я посмотрел, и я думаю, что это может быть ошибка. Мне нужно создать более простой тестовый пример (тот, который использует только Hibernate Reactive), чтобы убедиться в этом.
Хорошо, я разветвил проект и внес некоторые изменения. Теперь вроде работает: github.com/DavideD/ogram-interviews/commits/master
Я отправил pr в ваш репозиторий с исправлениями, которые должны заставить все работать. В конечном счете, я думаю, что ответ на ваш вопрос был правильным, но есть еще некоторые вещи, которые нужно исправить в проекте.
Здравствуйте, Давиде, я не могу отблагодарить вас за исправления и комментарии. Я обязательно проверю ссылки, которые вы предоставили по этому вопросу. По какой-то причине теперь при сохранении ответа происходит сбой с ошибкой (хотя тесты в порядке): Невозможно добавить или обновить дочернюю строку: сбой ограничения внешнего ключа (interviews
.answer
, CONSTRAINT FK8frr4bcabmmeyyu60qt7iiblo
FOREIGN KEY (question_id
) REFERENCES question
(id
) ON DELETE CASCADE)". Я пытаюсь понять, почему
Я не уверен в этом, но обычно это зависит от порядка удаления объектов. Я бы попытался удалить связанную ассоциацию, прежде чем удалять вовлеченный объект. Вероятно, такие вопросы лучше задавать в пользовательской теме Hibernate на Zulip: hibernate.zulipchat.com/#narrow/stream/132096-hibernate-user
Я пробовал разные способы, но все еще имею эту проблему с выборкой (случай «один ко многим», «многие ко многим» работает нормально). Я создал намного меньшую и более простую демонстрацию, если у вас еще есть время (и терпение...), пожалуйста, посмотрите: stackoverflow.com/questions/74391462/…
Ммм... Я начал смотреть на него и не вижу ничего плохого. Я не знаю, почему у вас возникает ошибка ленивой инициализации. Я не вижу никакой ошибки. Мне придется создать проект без kotlin и проверить, не повторится ли та же ошибка.
На вашем месте я бы создал проблему на github.com/quarkusio/quarkus/issues - я не думаю, что SO подходит для таких проблем.
Я пробовал оба варианта, которые вы предложили, но получаю ту же ошибку, как только в «Интервью» появляются связанные вопросы (т. е. до того, как я вставлю вопрос, он работает нормально, но после вставки и повторного извлечения выдает ошибку выше). Я создал демо на своем GitHub, посмотрите, пожалуйста, если возможно (я выложил ридми с инструкциями по воспроизведению): github.com/DnEgorWeb/ogram-interviews