I have made some changes to my MYSQL database so that a Product can have multiple Images associated with it (One to Many), however since making this change I am seeing some weird behaviour and the program throws a StackOverflow Exception
. It seems as if the program is stuck in a continuous loop before crashing and throwing the error.
My model classes are structured as follows:
@Entity
@Table(name="products")
public class Products {
public Products(String name, String price, String added_on, String category_id, String image_name, String description, List<ImageModel> imageModel) {
super();
this.name = name;
this.price = price;
this.added_on = added_on;
this.category_id = category_id;
this.image_name = image_name;
this.description = description;
this.imageModel = imageModel;
}
public Products(String name, String price, String added_on, String category_id, String description) {
super();
this.name = name;
this.price = price;
this.added_on = added_on;
this.category_id = category_id;
this.description = description;
}
public Products() {}
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
private String price;
private String added_on;
private String category_id;
private String image_name;
private String description;
private String image_id;
@ManyToOne(optional=false)
@JoinColumn(name = "category_id", insertable=false, updatable=false)
private Category category;
@OneToMany(mappedBy = "product")
private List<ImageModel> imageModel;
// Getters & Setters
This class is then linked to ModelImage
as follows:
@Entity
@Table(name = "image_table")
public class ImageModel {
public ImageModel() {
super();
}
public ImageModel(String name, String type, byte[] picByte, Products product) {
this.name = name;
this.type = type;
this.picByte = picByte;
this.product = product;
}
public ImageModel(String name, String type, byte[] picByte) {
this.name = name;
this.type = type;
this.picByte = picByte;
}
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name")
private String name;
@Column(name = "type")
private String type;
// image bytes can have large lengths so we specify a value
// which is more than the default length for picByte column
@Column(name = "picByte", length = 10000)
private byte[] picByte;
@ManyToOne
@JoinColumn(name="product_id")
private Products product;
// Getters Setters
When I add a product, the following code executes and seems to add everything as expected:
@PostMapping(value = "imageUploadMultiple")
public ResponseEntity<ImageResponse> addProductAndImages(@RequestParam("imageFiles") MultipartFile[] files, @RequestParam("productName") String productName, @RequestParam("productDescription") String productDescription, @RequestParam("productPrice") String productPrice, @RequestParam("categoryId") String categoryId) throws IOException {
// Need to save product first then get the id and save all images with the productId
Products products = productService.addProduct(productName, productDescription, productPrice, categoryId);
Arrays.asList(files).stream().forEach(file -> {
ImageModel img = null;
try {
img = new ImageModel(file.getOriginalFilename(), file.getContentType(), compressBytes(file.getBytes()), products);
imageRepository.save(img);
} catch (IOException e) {
e.printStackTrace();
}
});
return ResponseEntity.ok().build();
}
This endpoint accepts multiple images and form data which corresponds to the image(Product Details etc)
However, the problem occurs when I call the endpoint to get all products based on a particular categoryId
:
@RequestMapping(value = "getProductsByCategory", produces = MediaType.APPLICATION_JSON_VALUE)
public List<Products> getProductsByCategory(@RequestBody HashMap<String, String> request) {
String category_id = request.get("cat_id");
List<Products> list = productService.getProductsByCategory(category_id);
return list;
}
This then calls into the service class which then calls the repository code:
@Query("Select pro FROM Products pro WHERE pro.category_id=:cat_id")
List<Products> getByCategoryId(@Param("cat_id")String cat_id);
When I run the app in debug mode, I get the following data (At this time there is only one product for that particular categoryID):
Notice how 'ImageModel' is of a PersistentBag
type. When i dig deeper into this I get the mapped images to the particular product. In this instance there is 4 product images for the product. When i dig even deeper I notice there is just a continuous loop:
The error is as follows
java.lang.StackOverflowError: null
at java.lang.ClassLoader.defineClass1(Native Method) ~[na:1.8.0_251]
at java.lang.ClassLoader.defineClass(ClassLoader.java:756) ~[na:1.8.0_251]
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) ~[na:1.8.0_251]
at java.net.URLClassLoader.defineClass(URLClassLoader.java:468) ~[na:1.8.0_251]
at java.net.URLClassLoader.access$100(URLClassLoader.java:74) ~[na:1.8.0_251]
at java.net.URLClassLoader$1.run(URLClassLoader.java:369) ~[na:1.8.0_251]
Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: com.youtube.ecommerce.model.Products["imageModel"]->org.hibernate.collection.internal.PersistentBag[0]->com.youtube.ecommerce.model.ImageModel["product"]->com.youtube.ecommerce.model.Products["imageModel"]->org.hibernate.collection.internal.PersistentBag[0]->com.youtube.ecommerce.model.ImageModel["product"]->com.youtube.ecommerce.model.Products["imageModel"]->org.hibernate.collection.internal.PersistentBag[0]->com.youtube.ecommerce.model.ImageModel["product"]->com.youtube.ecommerce.model.Products["imageModel"]->org.hibernate.collection.internal.PersistentBag[0]->com.youtube.ecommerce.model.ImageModel["product"]->com.youtube.ecommerce.model.Products["imageModel"]->org.hibernate.collection.internal.PersistentBag[0]
The error just keeps going and going so i didnt post the full thing but it continuously says the same thing over and over again.
Im really struggling to understand whats gone wrong here!!
Its because when you will serialize your entity, you will have graph like
Product->Image[]->Product->Image[]->Product-> and so on
so you have to cut the recursion somewhere, eg using @JsonIgnore
or use @JsonManagedReference, @JsonBackReference`
This is exactly depicted on one of your images that you peek into the entity.