Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recursive JSON view of an entity with one-to-many relationship in REST controller

I'm using SpringBoot and JPA to build a REST interface.

Now, I have a strange JSON returned for the list of products fetched from the database. Let's say that I have:

@Entity
public class Product {

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

    @ManyToOne(optional = false, fetch = FetchType.LAZY)
    @JoinColumn(name = "categoryId", nullable = false, updatable = false)
    private Category category;

    ...
}

@Entity
public class Category implements Serializable {

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

    @OneToMany(mappedBy = "category", cascade = CascadeType.DETACH)
    @OrderBy("name ASC")
    private List<Product> products = Collections.emptyList();

    ...
}

The JPA repository for the Product is defined as:

public interface ProductRepository extends JpaRepository<Product, Long> {
    List<Product> findAll();
}

In my controller I have:

@Autowired
private ProductRepository productRepo;

@RequestMapping("/all-products", method = RequestMethod.GET)
public Map<String,Object> home() {
    Map<String,Object> model = new HashMap<String,Object>();
    model.put("products", productRepo.findAll());
    return model;
}

What is driving my crazy, is that if I try to call this service as follows:

$ curl localhost:8080/all-products

I get a recursive output due to the relationship between tables product and category, e.g.:

{"products":[{"id":1,"name":"Product1","category":
{"id":1,"name":"Cat1","products":[{"id":6,"name":"Product6","category":
{"id":1,"name":"Cat1","products":[{"id":6,"name":"Product6","category":
{"id":1,...

What am I doing wrong?

like image 973
JeanValjean Avatar asked Jul 16 '15 22:07

JeanValjean


1 Answers

You're not doing anything wrong (at least at the code level it's rather conceptual) - json serializer just goes like this:

  1. Product - serialize it, but wait - there is a category field, so serializer must serialize the category field
  2. Category - serialize it, but wait - there is a products field, so serializer must serialize each of the product in the list
  3. Product - because your collection contains the product & product contains category it goes in a endless loop untill a timeout.

You must use a view or just skip it.

  1. Use @JsonView

  2. Use a view as a POJO Return new ProductView that has all fields of product and a reference (category) to new CategoryView (you can end at this point) that has collection of (products) new ProductViewWithoutReferences, and so on

  3. Use @JsonIgnore on a collection of products

And as a side note - if it's a @RestController and you're invoking "all-products" then it's a bit unusual to return something else than a list. Wrapping the response in a map is redundant. Many rest clients expect a list when they invoke list() method.

like image 153
Xeon Avatar answered Nov 02 '22 10:11

Xeon