Как использовать @RestController (Spring) с дочерним списком объектов
Я пытаюсь создать службу REST с помощью Spring. Все работает, пока я не попытаюсь добавить список объектов (CartItem) в мой основной объект (Cart).
Это мой главный объект
@Entity
@Table(name="cart")
public class Cart implements Serializable{
...
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Id
@Column(name="id")
private Integer id;
/*when I add this I get the error. If I remove this, the
REST service works*/
@OneToMany(mappedBy="cart", fetch = FetchType.EAGER)
private List<CartItem> cartItems;
//getter, setter, constructors, other fields ecc.
}
Это объект внутри списка:
@Entity
@Table(name="cart_item")
public class CartItem implements Serializable{
...
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Id
@Column(name="id")
private Integer id;
@OneToOne(targetEntity = Product.class, cascade = CascadeType.ALL)
@JoinColumn(referencedColumnName="productId", name="product_id" )
private Product product;
@ManyToOne
@JoinColumn(name="cart_id", nullable=false)
private Cart cart;
//getter, setter, constructors, other fields ecc.
}
Это мой контроллер
@RestController
@RequestMapping(value="rest/cart")
public class CartRestController {
...
@RequestMapping(value = "/", method = RequestMethod.GET)
public List<Cart> readAll() {
return cartService.read();
}
...
}
Я получаю эту ошибку:
SEVERE: Servlet.service() for servlet [dispatcher] in context with path
[/webstore] threw exception [Request processing failed; nested exception
is org.springframework.http.converter.HttpMessageNotWritableException:
Could not write JSON: Infinite recursion (StackruError); nested
exception is com.fasterxml.jackson.databind.JsonMappingException:
Infinite recursion (StackruError) (through reference chain:...
Я предполагаю, что мне нужно было управлять списком внутри объекта Cart определенным образом, возможно, потому что я использую JPA, но я все еще не нашел решения в Интернете. Может кто-нибудь мне помочь?
2 ответа
Это проблема рекурсии сериализации, потому что CartItem имеет двунаправленное отображение обратно в корзину. Так что же происходит, что
- корзина сериализуется в JSON
- все CartItems внутри него сериализуются в JSON
- свойство Cart внутри CartItem получает сериализацию в JSON
- элементы CartItems внутри корзины сериализуются в JSON и т. д. и т. д.
- свойство Cart внутри CartItem получает сериализацию в JSON
- все CartItems внутри него сериализуются в JSON
Возможно, вы захотите исключить поле CartItem.cart из сериализации, пометив его @JsonIgnore
аннотаций.
Слишком легко представить слишком много информации внешнему миру, если вы используете сущности JPA непосредственно внутри своих веб-сервисов. У Джексона на самом деле есть полезная функция, которая называется JsonView, которая позволяет вам определять, какие свойства будут доступны, вы даже можете настроить их для каждого вызова веб-службы, если хотите.
Бесконечный список? Вы имели в виду исключение stackOverFlow?
Если ситуация такая же, как я сказал, тогда вы должны проверить что-то типа fetch и метод toString() или equal() сущностей или что-то в этом роде.
Например, существуют сущности с именами A и B, и их отношение одно ко многим (A - единственное). Если вы сконфигурируете оба их fetchType как Eager, то когда jpa запросит A, он также запросит B. Но B также содержит A, поэтому jpa снова будет запрашивать A. Этот тип циклического цикла вызовет stackOverFlow.
Кстати, как насчет предоставления дополнительной информации о вашей проблеме, такой как имя исключения? Мне слишком сложно дать вам конкретное решение, все, что я могу сделать, это рассказать вам о некоторых событиях, с которыми я встречался раньше.
Ну, я создал небольшой проект с SpringBoot 2.1.0 и MySql.
Это моя корзина
public class CartItem {
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Id
@Column(name="id")
private Integer id;
@JsonIgnore
@ManyToOne
@JoinColumn(name="cart_id", nullable=false)
private Cart cart;
}
и моя корзина:
public class Cart {
@GeneratedValue(strategy= GenerationType.IDENTITY)
@Id
@Column(name="id")
private Integer id;
@OneToMany(mappedBy="cart", fetch = FetchType.EAGER)
private List<CartItem> cartItems;
}
Контроллер такой же, как вы написали. После добавления @JsonIgnore в корзину, заполненную CartItem, круговой цикл завершен (до того, как я это сделал, в программе возникла проблема с круговым циклом).
Каждый раз, когда вы используете jpa с @oneToMany,@ManyToOne или @ManyToMany, вы должны быть осторожны с этой проблемой. Этот случай круговой ссылки может произойти при создании экземпляра объекта, печати объекта или чего-то подобного. И, конечно же, способ решить эту проблему, например, изменить тип выборки на LAZY, добавить @JsonIgnore, переопределить методы toString() и equal().