How to implement API returns nested JSON with OneToMany relationship in Spring Boot?
Asked Answered
M

2

6

I am developing simple API for practice project Online Shopping System. I am totally new in Spring Boot framework and creating API.

I want to return JSON similar to this:

[
{
    "id": 1,
    "name": "pname_46",
    "description": "pdesc_793_793_793_79",
    "price": 519.95,
    "details": [{"orderId": 10,
                 "productId": 1,
                 "quantity": 4
                }
                {"orderId": 12,
                 "productId": 1,
                 "quantity": 5
                }]
},
{
    "id": 2,
    "name": "pname_608",
    "description": "pdesc_874_874_874",
    "price": 221.7,
    "details": [{"orderId": 20,
                 "productId": 2,
                 "quantity": 2
                }
                {"orderId": 3,
                 "productId": 2,
                 "quantity": 67
                }]
}]

Here is my @Entity classes:

Product.java

@Entity
@Table(name = "Products")
public class Product implements Serializable {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "pcod")
private int id;

@Column(name = "pnam")
private String name;

@Column(name = "pdes")
private String description;

@Column(name = "price")
private Double price;

@OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Detail> details = new ArrayList<>();

//Constructor, setter, and getter ..

}

Detail.java

@Entity
@Table(name = "Details")
public class Detail {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "ordid")
private Order order;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "pcod")
private Product product;

@Column(name = "qty")
private int quantity;

//constructor, setters, and getters ..  
}

There is also class named Order.java similar to Product.java

ProductRepository.java

@Repository
public interface ProductRepository extends JpaRepository<Product, Integer> {

}

OnlineShoppingApiController.java

@RestController
public class OnlineShoppingApiController {
@Autowired
ProductRepository productRepository;

@GetMapping("/products")
public List<Product> getAllProducts(){
    return productRepository.findAll();
}

@GetMapping("/products/id={id}")
public Optional<Product> getOneProduct(@PathVariable String id){
    int pid = Integer.parseInt(id);
    return productRepository.findById(pid);
}
}

ProjectApplication.java

@SpringBootApplication
public class ProjectApplication {

public static void main(String[] args) {
    SpringApplication.run(ProjectApplication.class, args);
}
}

This program gets data from MySql database. There are stored data in tables.

Tables look like this:
Products:
- pcod
- pnam
- pdes
- price

Details:
- ordid
- pcod
- qty

Here is my pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.example</groupId>
<artifactId>project</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>project</name>
<description>Demo project for Spring Boot</description>

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.0.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

When I run the application and check the API using POSTMAN, I am getting this result:

{
"timestamp": "2018-04-04T13:39:44.021+0000",
"status": 500,
"error": "Internal Server Error",
"message": "Could not write JSON: could not extract ResultSet; nested exception is com.fasterxml.jackson.databind.JsonMappingException: could not extract ResultSet (through reference chain: java.util.ArrayList[0]->com.example.project.pojo.Product[\"details\"])",
"path": "/products"

}

How can I solve this problem?

Thanks for the answer

Meretricious answered 4/4, 2018 at 13:49 Comment(0)
W
6

When your Product entity is being converted to Json, the product have a List of Details, the details are converted to Json as well but they are referencing the Product again and this starts and endless loop and you get the error.

A solution could be to add a @JsonIgnore in one side of the relationship

@Entity
public class Detail {
    ...
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "pcod")
    @JsonIgnore
    private Product product;
    ...
}
Whim answered 4/4, 2018 at 14:4 Comment(1)
It finally works. Thank you so much DavidMeretricious
B
1

Using @JsonManagedReference and @JsonBackReference annotations in the two entities can solve the problem ,as well. please refer to this article about Jackson bidirectional relationships.

Billibilliard answered 9/7, 2019 at 8:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.