Двунаправленные отношения приводят к ошибке переполнения стека

@Entity
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Portfolio {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer id;
    
    @OneToMany(mappedBy = "parent" ,fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
    @JsonManagedReference
    private Set<PortfolioChildMap> parents;
    
}
@Entity
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PortfolioChildMap {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer id;

    @ManyToOne
    @JsonBackReference
    @JoinColumn(name = "parent_id")
    private Portfolio parent;

    @ManyToOne
    @JoinColumn(name = "child_id")
    private Portfolio child;
}

@Data
@Builder
public class PortfolioResponseDto {
    private Integer id;
    private List<PortfolioResponseDto> children;

    public static PortfolioResponseDto toDTO(Portfolio entity) {
        return PortfolioResponseDto.builder()
                .id(entity.getId())
                .children(entity.getParents().stream().map(parent-> toDTO(parent.getChild())).collect(Collectors.toList()))
                .build();
    }
}
public class PortfolioService  {

    private final PortfolioRepository portfolioRepository;

    @Override
    public List<PortfolioResponseDto> getAllPortfolios() {
        return portfolioRepository.findAll().stream()
                .map(PortfolioResponseDto::toDTO).collect(Collectors.toList());
    }

}

Таблица портфолио_child_map

Как вы можете видеть, между портфолио с идентификаторами 33 и 34 есть кружок. Родительский Portfolio.id=33 имеет дочерний Portfolio.id=34, а также Родительский Portfolio.id=34 имеет дочерний Portfolio.id=33, и это круг.

Когда я вызываю PortfolioService.getAllPortfolios(), это вызывает ошибку в методе PortfolioResponseDto.toDTO.

Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.StackOverflowError] with root cause java.lang.StackOverflowError: null

Я почти уверен, что это распространенная ошибка, но я не знаю, как ее исправить. Итак, у меня есть два вопроса:

  1. Как правильно исправить эту проблему, как избежать подобных ошибок.

  2. В моем случае это нормально, что я получил первого ребенка от родителя и остановился. Я не знаю, как это сделать. Должен ли я реализовать собственный метод findAll в репозитории?

У меня есть два комментария: во-первых, похоже, что вы используете рекурсивный дизайн, и, возможно, это поможет сформулировать ваш запрос: web.csulb.edu/colleges/coe/cecs/dbdesign/…. А во-вторых, мне кажется странным, что X может быть родителем Y и в то же время противоположным (Y родителем X). Реален ли такой сценарий в вашей сфере?

silver_mx 22.05.2024 21:23

да, это возможен сценарий в моем домене

Romillion 28.05.2024 18:55
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
2
102
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

  1. Решите, что это недопустимый вариант использования, и исправьте свои данные.
  2. Обнаружение бесконечной рекурсии. Если это допустимый вариант использования, прервите рекурсивный алгоритм при обнаружении цикла. (Например, вы можете передать набор в свой рекурсивный метод. Начните с пустого набора для первоначального вызова, а затем проверьте, обработал ли вы уже текущий элемент. Если нет, добавьте в набор и продолжайте. Если уже обработано , прерывание).
  3. Измените свой вариант использования. Вместо преобразования всей глубины в DTO отобразите только один уровень. Затем, если клиенту/вызывающему абоненту требуется информация о следующем уровне, он может запросить ее посредством другого вызова. Это возлагает ответственность за обнаружение рекурсии на вызывающую сторону.

Третий вариант «карта одного уровня» — именно то, что мне нужно. Знаете ли вы, как это реализовать в моем случае? Я предполагаю, что мне нужно удалить свойства родителей и активов из сущности портфеля и реализовать собственный метод на уровне репозитория, чтобы получить первого дочернего элемента?

Romillion 28.05.2024 19:00

Сопоставьте идентификатор родителя (возможно, также немного дополнительных метаданных, если вы этого хотите или нуждаетесь в этом). Тогда вообще не картируйте детей. Если вы хотите или нуждаетесь в детях, позвоните отдельно. В случае, когда вам нужен родительский элемент, у вас есть идентификатор родителя, и вы можете просто загрузить его по идентификатору. В случае, когда вам нужны дочерние элементы, вам нужен репозиторий, в который вы можете просто загрузить текущий идентификатор, а затем перейти к дочерним элементам. Затем вы можете сопоставить их и вернуть результат.

Nathan 30.05.2024 08:02

Другие вопросы по теме