Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

like image 941
Tillo Avatar asked Mar 06 '23 12:03

Tillo


2 Answers

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;
    ...
}
like image 190
David SN Avatar answered Mar 24 '23 19:03

David SN


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

like image 25
Udith Indrakantha Avatar answered Mar 24 '23 19:03

Udith Indrakantha