简体   繁体   中英

What is best practice in using @OneToMany relation with @RestController without infinite recutsion

I have following:

@Data
@Entity
@Table(name = "floor")
public class Floor {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @Column(name = "floor_id")
    private Long id;

    @JsonIgnoreProperties(value= {"floor", "registrations"})
    @OneToMany (mappedBy = "floor", cascade = CascadeType.ALL)
    private Set<Slot> slots;
}

@Entity
@Table(name = "slot")
public class Slot {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @Column(name = "slot_id")
    private Long id;

    @ManyToOne
    @JoinColumn(name = "floor_id")
    private Floor floor;
}

@RestController
public class Controller {

    @Autowired
    FloorService floorService;

    @GetMapping("/api/floors")
    public List<Floor> getAllFloors() {
        // calls repository.findAll()
        return floorService.getAllFloors();
    }
}

When accessing API endpoint, I get:

2021-03-06 06:52:30.038  WARN 699889 --- [tp1028811481-19] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: java.util.ArrayList[0]->com.veebzone.parking.model.Floor["slots"])]

By getting list of each floor with /api/floors endpoint, my goal is to get list of slots associated with each floor within their JSON element.

What is the best practice to achieve this:

  1. My approach is correct, just some configuration issue?
  2. I should use some DTO returned by a service instead (that has the slots list as a child element) and use JSONIgnore for the slots field.
  3. Something else

Using a DTO would be the best, so you can decouple your database classes from the REST API.

If you want the quick solution, you can use Jackson's @JsonManagedReference and @JsonBackReference . This will tell Jackson that there's a recursion / bidirectional reference, and how to serialize it:

class Floor {
  @JsonManagedReference
  Set<Slot> slots;
}

class Slot {
  @JsonBackReference
  Floor floor;
}

I'm assuming you want something like this (an array of floors, each one with an array of slots) when you call the endpoint:

[
    { "id": 1,
      "slots": [
            { "id": 1 },
            { "id": 2 }
        ]
    },
    { "id": 2,
      "slots": [
            { "id": 3 }
        ]
    }
]

You have an Infinite recursion because Jackson, the default library used in Spring Boot to write/reads Json (like in your enpoint), tries to use every field in your class to build the Json.

So what is happening is that Jackson is trying to write the first floor in the output Json, but this floor relates to the first slot, which relates to the first floor and so on. There's your loop.

If you want to break the loop you have 2 choices:

  1. Remove @JsonIgnoreProperties(value= {"floor", "registrations"}) in the Floor class, then add @JsonIgnore over the floor field in the Slot class. This way, Jackson won't serialize that field.
  2. Build your own Serializer for the Floor class using @JsonSerialize (see this ).

Now, which option is better will depend on how large your entities are and how complex the relation between entities will be.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM