0

Please be patient with me. tried my best to explain with sample easy code.

Two Entities - Shop and Product.

Relationship - A Shop can have many Product.

I return a Shop object, it keeps printing like this -

{
    "shopId": 1,
    "shopName": "S1",
    "productList": [
        {
            "productId": 100,
            "productName": "MOBILE",
            "shop": {
                "shopId": 1,
                "shopName": "S1",
                "productList": [
                    {
                        "productId": 100,
                        "productName": "MOBILE",
                        "shop": {

Before i start with the actual issue, i did solve the Cyclic issue partially but arrived at a new problem. I stopped it with the help of @JsonIgnore

Basically when i print my parent(Shop) json object i stopped the cyclic response by using @JsonIgnore in child (Product) class field.

@JsonIgnore 
private Shop shop

So, now API 1 =

@GetMapping("/getShopById")
    public Shop getShopById(){
        return shopRepo.findById(1L).get();
    }

GIVES ME OUTPUT - (Which is perfect as i avoid printing Shop back);

{
    "shopId": 1,
    "shopName": "S1",
    "productList": [
        {
            "productId": 100,
            "productName": "MOBILE"
        },
        {
            "productId": 101,
            "productName": "EARPHONE"
        }
    ]
}

But now anytime i want to fetch the Shop from a Product object and send the response i get an error, which is because of the @JsonIgnore i guess, which basically is completely stopping the serialization of the field from Product object.

API 2 =

@GetMapping("/getShopFromTheProductId")
    public Shop getShopFromProductId() {
        Shop s = productRepo.findById(100L).get().getShop();
        return s;
    }

GIVES ME ERROR -


com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: com.doubt.StackOverFlow.Shop$HibernateProxy$YEW0qvzw["hibernateLazyInitializer"])
    at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77) ~[jackson-databind-2.12.3.jar:2.12.3]
    at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1276) ~[jackson-databind-2.12.3.jar:2.12.3]
    at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:400) ~[jackson-databind-2.12.3.jar:2.12.3]
    at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.failForEmpty

So to summarize how can i ignore printing/getting the Parent back from Child until and unless i require it explicitly ?

PROBABLE SOLUTION 1 - remove the getter for Shop (private Shop getShop()) from Product entity . But this is not a solution for me as i will never be able to track back to the parent when i may need it in business logic.

MY classes -

Controller -

@RestController
public class MainController {

    @Autowired
    private ShopRepo shopRepo;
    @Autowired
    private ProductRepo productRepo;
    
    @GetMapping("/getShopById")
    public Shop getShopById(){
        return shopRepo.findById(1L).get();
    }
    
    @GetMapping("/getShopFromTheProductId")
    public Shop getShopFromProductId() {
        Shop s = productRepo.findById(100L).get().getShop();
        return s;
    }
}

Shop Entity -

@Entity
@Table(name = "SHOP")
public class Shop {

    @Id
    @Column(name = "SHOP_ID")
    private Long shopId;
    
    @Column(name = "SHOP_NAME")
    private String shopName;
    
    @OneToMany(fetch = FetchType.LAZY,targetEntity = Product.class, mappedBy = "shop")
    private List<Product> productList;
........

all the getters and setters

Product Entity -

@Entity
@Table(name = "PRODUCT")
public class Product {

    @Id
    @Column(name = "PRODUCT_ID")
    private Long productId;
    
    @Column(name = "PRODUCT_NAME")
    private String productName;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "SHOP_ID")
    @JsonIgnore
    private Shop shop;
........
all getters and setters
  • So i have come to terms with this problem. Firstly this is wrong sending a domain object directly as a response. Best practice is to have a RequestShopDTO Object and Similarly and ResponseShopDTO. We should have them with getters and setters same as the domain object. P.S. - DTO - Data Transfer Object – Arunabha Ghosh Jun 18 '21 at 10:00

2 Answers2

0

To avoid the cyclic problem Use @JsonManagedReference, @JsonBackReference as below.

Add @JsonManagedReference on Parent class, shop entity.

@JsonManagedReference
@OneToMany(fetch = FetchType.LAZY,targetEntity = 
Product.class, mappedBy = "shop")
private List<Product> productList;

Add @JsonBackReference on child class as below.

@JsonBackReference
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "SHOP_ID")
@JsonIgnore
private Shop shop;
S. Anushan
  • 728
  • 1
  • 6
  • 12
  • Doesn't solve the issue. `@JsonBackReference` is same as `@JsonIgnore` , only diff is `@JsonBackReference` is used with a Parent field in a child. It is just a better practice. So as per your solution. i still get the same issue, while trying to fetch the Parent from the Child Object (still gives same error when fetching `Shop` from `Product`). – Arunabha Ghosh Jun 18 '21 at 07:17
  • The main purpose of JsonRefernce avoid cyclic problem. What version of jackson your using. And can you try 2.9.8 version. ( Your jar missing some Library seems like ) – S. Anushan Jun 18 '21 at 16:24
  • using 2.12 version of jackson – Arunabha Ghosh Jun 19 '21 at 06:39
  • Upgrade to some new version like 2.9.8 – S. Anushan Jun 19 '21 at 08:52
  • @S.Anushan you do know how semantic versioning works right? 2.12 is the newer version – glace Sep 07 '21 at 08:14
0

So i have come to terms with this problem. Firstly this is wrong sending a domain object directly as a response. Not at all the best practice. Best practice is to have a RequestShopDTO Object and Similarly and ResponseShopDTO. We should have DTO with getters and setters same as the domain object, in this case Shop.

  1. Rest API should receive RequestShopDTO object.
  2. Use a factory class/ adapter classes to convert the RequestShopDTO to a Shop Domain object and forward it to the Business layer.
  3. Similarly we should convert the Response Shop domain object to ResponseShopDTO object and send it as response.
  4. we should have like BaseRequest class, extended by something like CreateRequest, UpdateRequest, GetRequest etc... where properties common to all get requests are in GetRequest which is then extended by more specific request classes such as RequestShopDTO.
  5. Similarly we can have a abstract Adapter class like this RequestDtoToDomainBaseAdapter that gets extended by something like ShopDtoToShopDomainAdapter.

reference - inor's answer - Design Pattern to model Request and Response Objects for Webservices P.S. - DTO - Data Transfer Object