Не удалось решить проблему с циклической ссылкой с контроллером JPA и REST весной

#java #spring #spring-boot #jackson #jackson-databind

Вопрос:

Я работаю над @RestController классом, который должен возвращать данные JSON с Teacher обработкой массива из 1 или более Section s.

В настоящее время у меня есть только 2 отдельные записи в teacher_section таблице

Я верю, что эти отношения :

  • 1 Teacher может иметь МНОГО обработанных Section (ов)
  • 1 Section может обрабатываться МНОГИМИ различными Teacher (- ами)

Что заставило меня подумать, что правильная аннотация отношений для использования @ManyToMany

Таблицы базы данных

 CREATE TABLE `teacher_section` (
  `teacher_id` int(11) NOT NULL,
  `section_id` int(11) NOT NULL,
  KEY `teacher_id` (`teacher_id`),
  KEY `section_id` (`section_id`),
  CONSTRAINT `teacher_section_ibfk_2` FOREIGN KEY (`teacher_id`) REFERENCES `teacher` (`id`),
  CONSTRAINT `teacher_section_ibfk_3` FOREIGN KEY (`section_id`) REFERENCES `section` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='This is a join table';

CREATE TABLE `teacher` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `employee_id` int(11) NOT NULL,
  `first_name` varchar(45) NOT NULL,
  `middle_name` varchar(45) NOT NULL,
  `last_name` varchar(45) NOT NULL,
  `is_adviser` bit(1) NOT NULL DEFAULT b'0',
  `is_active` bit(1) NOT NULL DEFAULT b'1',
  `date_created` timestamp NOT NULL DEFAULT current_timestamp(),
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;

CREATE TABLE `section` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `section_name` varchar(45) NOT NULL,
  `is_active` bit(1) NOT NULL DEFAULT b'1',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
 

Классы Java ниже.

Абстрактность

 @MappedSuperclass 
public class AbstractEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name="is_active")
    private boolean isActive;

//getters and setters....
}
 

Учительница

 @Entity
@Table(name="teacher")
public class Teacher extends AbstractEntity{
    
    @Column(name="employee_id")
    private int employeeId;
    
    @Column(name="first_name")
    private String firstName;
    
    @Column(name="middle_name")
    private String middleName;
    
    @Column(name="last_name")
    private String lastName;
    
    @Column(name="is_adviser")
    private boolean isAdviser;
    
    @Column(name="date_created")
    private Timestamp dateCreated;
    
    @ManyToMany(cascade = {
            CascadeType.PERSIST,
            CascadeType.MERGE
        })
    @JoinTable(
              name = "teacher_section", 
              joinColumns = @JoinColumn(name = "teacher_id"), 
              inverseJoinColumns = @JoinColumn(name = "section_id"))
    Set<Section> sections;

    //getters and setters.....
}
 

Section

 @Entity
@Table(name="section")
public class Section extends AbstractEntity{
    
    @Column(name = "section_name")
    private String sectionName;
    
    @ManyToMany (mappedBy = "sections")
    private Set<Teacher> teachers;

    //getters and setters...
}
 

TeacherRestController

 @RestController
@RequestMapping("/teachers")
@CrossOrigin(origins = "http://localhost:4200")
public class TeacherRestController {

    @Autowired
    TeacherRepository teacherRepository;
    
    
    @GetMapping
    public List<Teacher> getTeachers(){
        //GET localhost:8080/grading/teachers
        return teacherRepository.findAll();
    }
}
 

TeacherRepository

 public interface TeacherRepository extends JpaRepository<Teacher, Long>{

}
 

PROBLEM

Конечная точка GET localhost:8080/grading/teachers возвращает данные json, вложенные Teacher в Section несколько раз.

Ответ в Postman выглядит так:

 [
    {
        "id": 1,
        "employeeId": 20210001,
        "firstName": "John",
        "middleName": "Someone",
        "lastName": "Doe",
        "dateCreated": "2021-08-29T13:13:29.000 00:00",
        "adviser": false,
        "section": [
            {
                "id": 1,
                "sectionName": "Unity",
                "teacher": [
                    {
                        "id": 1,
                        "employeeId": 20210001,
                        "firstName": "John",
                        "middleName": "Someone",
                        "lastName": "Doe",
                        "dateCreated": "2021-08-29T13:13:29.000 00:00",
                        "adviser": false,
                        "section": [
                            {
                                "id": 1,
                                "sectionName": "Unity",
                                "teacher": [
                                    {
                                        "id": 1,
                                        "employeeId": 20210001,
                                        "firstName": "John",
                                        "middleName": "Someone",
                                        "lastName": "Doe",
                                        "dateCreated": "2021-08-29T13:13:29.000 00:00",
                                        "adviser": false, .....
 

Я не могу понять, в чем дело. Я даже попытался следовать некоторым онлайн-руководствам о том, как правильно реализовать @ManyToMany с 2 веб-сайтов ниже, но все еще не смог решить проблему.

https://www.baeldung.com/jpa-many-to-many

https://vladmihalcea.com/the-best-way-to-use-the-manytomany-annotation-with-jpa-and-hibernate/

Я использую среду разработки Spring Tool Suite IDE и MySQL в качестве базы данных.

Заранее спасибо.

Комментарии:

1. Вы должны решить, какие отношения вы хотите сериализовать. Пожалуйста, прочтите baeldung.com/…

2. Вам нужно @JsonIdentityInfo , или лучше, отдельное представление DTO.

3. @SimonMartinelli спасибо, что поделились ссылкой. В нем есть много информации, о которой я еще не знаю.

4. @chrylis-осторожно оптимистичный — @JsonIdentityInfo Решил мою проблему! Большое спасибо!! Я прочитаю больше об аннотациях, связанных с Json и альтернативами экспериментов.

5. Не используйте объекты сущностей в контроллере, это очень плохая практика. Создайте отдельные объекты dto и объекты карты.

Ответ №1:

Вы должны использовать @JsonManagedReference и @JsonBackReference аннотации. Первый-в корневом объекте-владельце, а второй — в дочернем объекте.

в Учителе:

 @JsonManagedReference
Set<Section> sections;
 

в разделе:

 @JsonBackReference
private Set<Teacher> teachers;
 

Альтернативой является добавление @JsonIgnore в teachers поле in Section .