Вложенная проблема «один ко многим» в spring data JPA

#java #hibernate #jpa #spring-data-jpa

#java #переход в спящий режим #jpa #spring-data-jpa

Вопрос:

Таблицы:

 survey(id, title);
survey_question(id, survey_id, title);
survey_question_option(id, survey_question_id, content)
  

Сущности:

 @Entity
public class Survey implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    @OneToMany(mappedBy = "survey", fetch = FetchType.EAGER, orphanRemoval = true, cascade = CascadeType.ALL)
    private List<SurveyQuestion> questions;

}

@Entity
public class SurveyQuestion implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @JoinColumn(nullable = false)
    @ManyToOne
    @JsonIgnore
    private Survey survey;

    private String title;

    @OneToMany(mappedBy = "surveyQuestion", fetch = FetchType.EAGER, orphanRemoval = true, cascade = CascadeType.ALL)
    private List<SurveyQuestionOption> options;

}

@Entity
public class SurveyQuestionOption implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @JoinColumn(nullable = false)
    @ManyToOne
    @JsonIgnore
    private SurveyQuestion surveyQuestion;

    private String content;

}

  

Теперь добавьте опрос

 @PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Survey create(@RequestBody Survey survey) {
    return repository.save(survey);
}
  

JSON в теле запроса

 {
    "title": "I'm a survey!",
    "questions": [{
        "title": "I'm a question!",
        "options": [{
            "content": "I'm an option."
        },
        {
            "content": "I'm an option."
        },
        {
            "content": "I'm an option."
        },
        {
            "content": "I'm an option."
        }]
    },
    {
        "title": "I'm a question!",
        "options": [{
            "content": "I'm an option."
        },
        {
            "content": "I'm an option."
        },
        {
            "content": "I'm an option."
        },
        {
            "content": "I'm an option."
        }]
    }]
}
  

Успех, затем таблицы, подобные этой:

опрос:

 id    title
---------------------------------------
46    I'm a survey!
  

survey_question:

 id    survey_id    title
---------------------------------------
34    46           I'm a question!
35    46           I'm a question!
  

survey_question_option:

 id    survey_question_id    content
---------------------------------------
17    34                    I'm an option!
18    34                    I'm an option!
19    34                    I'm an option!
20    34                    I'm an option!
21    35                    I'm an option!
22    35                    I'm an option!
23    35                    I'm an option!
24    35                    I'm an option!
  

Now, when I get all surveys by page

 @GetMapping
public Page<Survey> findAll(Pageable page) {
    return repository.findAll(page);
}
  

The response is correct, 2 questions, 4 options each question

 {
    "content": [{
        "id": 46,
        "title": "I'm a survey!",
        "questions": [{
            "id": 34,
            "title": "I'm a question!",
            "options": [{
                "id": 17,
                "content": "I'm an option."
            },
            {
                "id": 18,
                "content": "I'm an option."
            },
            {
                "id": 19,
                "content": "I'm an option."
            },
            {
                "id": 20,
                "content": "I'm an option."
            }]
        },
        {
            "id": 35,
            "title": "I'm a question!",
            "options": [{
                "id": 21,
                "content": "I'm an option."
            },
            {
                "id": 22,
                "content": "I'm an option."
            },
            {
                "id": 23,
                "content": "I'm an option."
            },
            {
                "id": 24,
                "content": "I'm an option."
            }]
        }]
    }],
    "page": 1,
    "size": 20,
    "totalPages": 1,
    "totalCount": 1
}
  

НО, когда я получаю один опрос по идентификатору, подобному этому:

 @GetMapping("/{id:\d }") // 46
public Survey get(@PathVariable Long id) {
    return repository.findById(id).orElse(null);
}
  

Ответ сбивает меня с толку, всего 8 вопросов

 {
    "id": 46,
    "title": "1111111111111",
    "questions": [{
        "id": 34,
        "title": "I'm a question!",
        "options": [{
            "id": 17,
            "content": "I'm an option."
        },
        {
            "id": 18,
            "content": "I'm an option."
        },
        {
            "id": 19,
            "content": "I'm an option."
        },
        {
            "id": 20,
            "content": "I'm an option."
        }]
    },
    {
        "id": 34,
        "title": "I'm a question!",
        "options": [{
            "id": 17,
            "content": "I'm an option."
        },
        {
            "id": 18,
            "content": "I'm an option."
        },
        {
            "id": 19,
            "content": "I'm an option."
        },
        {
            "id": 20,
            "content": "I'm an option."
        }]
    },
    {
        "id": 34,
        "title": "I'm a question!",
        "options": [{
            "id": 17,
            "content": "I'm an option."
        },
        {
            "id": 18,
            "content": "I'm an option."
        },
        {
            "id": 19,
            "content": "I'm an option."
        },
        {
            "id": 20,
            "content": "I'm an option."
        }]
    },
    {
        "id": 34,
        "title": "I'm a question!",
        "options": [{
            "id": 17,
            "content": "I'm an option."
        },
        {
            "id": 18,
            "content": "I'm an option."
        },
        {
            "id": 19,
            "content": "I'm an option."
        },
        {
            "id": 20,
            "content": "I'm an option."
        }]
    },
    {
        "id": 35,
        "title": "I'm a question!",
        "options": [{
            "id": 21,
            "content": "I'm an option."
        },
        {
            "id": 22,
            "content": "I'm an option."
        },
        {
            "id": 23,
            "content": "I'm an option."
        },
        {
            "id": 24,
            "content": "I'm an option."
        }]
    },
    {
        "id": 35,
        "title": "I'm a question!",
        "options": [{
            "id": 21,
            "content": "I'm an option."
        },
        {
            "id": 22,
            "content": "I'm an option."
        },
        {
            "id": 23,
            "content": "I'm an option."
        },
        {
            "id": 24,
            "content": "I'm an option."
        }]
    },
    {
        "id": 35,
        "title": "I'm a question!",
        "options": [{
            "id": 21,
            "content": "I'm an option."
        },
        {
            "id": 22,
            "content": "I'm an option."
        },
        {
            "id": 23,
            "content": "I'm an option."
        },
        {
            "id": 24,
            "content": "I'm an option."
        }]
    },
    {
        "id": 35,
        "title": "I'm a question!",
        "options": [{
            "id": 21,
            "content": "I'm an option."
        },
        {
            "id": 22,
            "content": "I'm an option."
        },
        {
            "id": 23,
            "content": "I'm an option."
        },
        {
            "id": 24,
            "content": "I'm an option."
        }]
    }]
}
  

Пожалуйста, скажите мне, как я могу решить эту проблему?

Ответ №1:

Вы используете fetch = FetchType.EAGER для обоих

 private List<SurveyQuestion> questions;
  

и

 private List<SurveyQuestionOption> options;
  

Таким образом, вы всегда извлекаете все дерево здесь по умолчанию.

Теперь ключевым моментом здесь является то, что вы объявляете эти зависимости как List . Это означает упорядоченный, но допускающий дубликаты. Здесь вы получаете дублированный вопрос по количеству его вариантов.

Попробуйте использовать Set или SortedSet , чтобы избежать дубликатов.