Я обыскал весь Интернет, эти трюки не решили мою проблему. Вот моя сущность (упрощенная версия):
@Entity
@Table(name = "msg")
public class InternalMessage implements Serializable {
private static final long serialVersionUID = 3768430013233258L;
@PrePersist
public void genTicketId() {
// something
}
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", unique = true)
private Long id;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "internal_msg_parent_fk", nullable = true)
private InternalMessage parent = null;
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<InternalMessage> childMessages = new ArrayList<>();
}
Я не вижу никаких проблем, и до того, как я перешел на Spring boot 3 (спящий режим 6), все работало нормально.
Так в чем же может быть проблема?
Редактировать:
Mysql: 8.0.33
Spring boot: 3.3.3
Vscode 1.92.2
gradle: 8.10
Тот же результат: запустите приложение в режиме отладки в vscode или командной строке: ./gradlew bootRun
Хорошо, у меня есть идея, в случае сбоя выглядит следующий код:
public Predicate toPredicate(Root<InternalMessage> root, CriteriaQuery<?> query,
CriteriaBuilder criteriaBuilder) {
List<Predicate> predicates = new ArrayList<>();
if (search == null || search.trim().isEmpty()) {
// this part is causing me issue
Expression<InternalMessage> parent = root.get("parent").as(InternalMessage.class);
Predicate p = criteriaBuilder.isNull(parent);
predicates.add(p);
query.distinct(true);
return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
} else {
// this part works
}
}
и это строка, вызывающая исключение:
Expression<InternalMessage> parent = root.get("parent").as(InternalMessage.class);
Что-то не так с приведенной выше строкой? Это работало в более старой весенней загрузке/спящем режиме.
Спасибо Дхавал, только что обновил пост подробными версиями.
Можете ли вы также предоставить еще несколько строк трассировки стека исключений, пожалуйста?
@FrancescoPoli добавил. Но пришлось удалить зависимости.
ок, удалил нижнюю часть трассировки стека и добавил зависимости.
ОК, добавлено больше информации
Я заметил это только после вашего последнего обновления: столбец соединения (где, как я полагаю, сохраняется идентификатор родительского элемента) называется «родительским», а атрибут InternalMessage также называется «родительским»? Может ли это псевдонимирование привести к тому, что Hibernate неправильно оценит то, что управляет, а затем попытается неправильно обработать родительский экземпляр? Действительно, в данном случае это вина Hibernate...
Просто ради любопытства... пробовали ли вы установить @JoinColumn(name = "PARENT", nullable = true)
(обратите внимание на верхний регистр имени столбца)?
Спасибо @FrancescoPoli, это не так, для простоты я использовал «родительский», но имя столбца на самом деле другое: internal_msg_parent_fk
. Сейчас обновляю пост.
Почему вы моделируете свою сущность двунаправленной? Есть ли вариант использования, который нельзя охватить путем моделирования сверху вниз (только удержание дочерних сообщений) (родительский элемент все равно может быть частью таблицы!).
@ValerijDobler Изначально у меня был только parent
, у меня нет List...
, и он отлично работал при весенней загрузке 2.6.4 (которая была версией до миграции). Я добавил вторую часть только из-за исключения, с которым столкнулся сейчас (и в некоторых сообщениях говорится, что это может решить проблему, но это не так)
Мне удалось воспроизвести вашу ошибку локально. Я создал реализацию интерфейса Specification
, чтобы иметь почти одинаковый метод toPredicate
и использование контекста. Подтверждаю также, что ошибка происходит из строки:
Expression<InternalMessage> parent = root.get("parent").as(InternalMessage.class);
В любом случае мне удалось решить проблему заменой строк:
Expression<InternalMessage> parent = root.get("parent").as(InternalMessage.class);
Predicate p = criteriaBuilder.isNull(parent);
С помощью этого единственного утверждения удаляем промежуточный шаг Expression
:
Predicate p = criteriaBuilder.isNull(root.get("parent"));
Я не уверен, приводит ли это к правильному извлечению данных, но я вижу, что оператор sql, который генерируется в тестах junit, имеет следующий вид:
select distinct im1_0.id,im1_0.internal_msg_parent_fk from msg im1_0 where im1_0.internal_msg_parent_fk is null
Кажется, это то, что требуется в оригинальном методе toPredicate
.
Позвольте мне посмотреть, решит ли это проблему в вашем приложении.
Для полноты картины оригинальный метод работает, если вместо Long
используется InternalMessage
, следующим образом:
Expression<Long> parent = root.get("parent").as(Long.class);
но я думаю, что приведенное выше решение более понятно, в том числе потому, что при использовании этого подхода сгенерированный оператор выбора выглядит следующим образом:
select distinct im1_0.id,im1_0.internal_msg_parent_fk from msg im1_0 where cast(im1_0.internal_msg_parent_fk as bigint) is null
где есть это приведение (... как bigint), которое в данном случае совершенно ненужно.
Спасибо @Франческо!!! Это действительно решило проблему. Оба предложения работают! Да, первый проще и лучше. Я думаю, это может быть связано с этой проблемой: stackoverflow.com/questions/78913982 и stackoverflow.com/a/76522587/14072498 Большое спасибо!!!
Да, действительно. Возможно, это те же побочные эффекты, что и у других вопросов: stackoverflow.com/questions/78844558 и stackoverflow.com/questions/77648311, какой беспорядок...
Можете ли вы подробно указать, какую версию весенней загрузки вы используете. Предоставьте файл конфигурации maven/gradle. Также укажите, какую базу данных вы используете? Я попробовал ваш код с весенней загрузкой 3.3.2 и базой данных h2, ошибок не обнаружено.