Child Object has parent Object as attribute JPA caused endless JSON - java

I have the following defined within a parent object CommentTarget:
// bi-directional many-to-one association to EmployerDetails
#OneToMany(mappedBy = "commentTarget", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Comment> comments;
and this defined within the Child Comment:
#ManyToOne(cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH} )
#JoinColumn(name = "comment_target_id")
private CommentTarget commentTarget;
However when I take the list from the target and return as a JSON:
#RestController
#RequestMapping("/ticketV2")
public class TicketV2Controller {
#Autowired
CommentTargetService commentTargetService;
#RequestMapping(value = "/{ticketId}/comments", method = RequestMethod.GET)
public List<Comment> getTicketComments(#PathVariable(value="ticketId") String id,
#RequestParam String type){
CommentTarget commentTarget = commentTargetService.findByTargetIdAndTargetType(Long.valueOf(id), TargetName.valueOf(type));
List<Comment> commentsList = commentTarget.getComments();
return commentsList;
}
It craps out as it keeps the reference to the target then the list within it and so on and so on:
[
{
"id": 997,
"commentedBy": 1,
"commenterName": "Exchange Admin",
"comment": "123456",
"commentTarget": {
"id": 703,
"targetId": 216,
"targetName": "TICKET",
"created": 1586548428358,
"updated": 1586548428358,
"comments": [
{
"id": 997,
"commentedBy": 1,
"commenterName": "Exchange Admin",
"comment": "123456",
"commentTarget": {
"id": 703,
"targetId": 216,
"targetName": "TICKET",
"created": 1586548428358,
"updated": 1586548428358,
"comments": [
{
"id": 997,
"commentedBy": 1,
"commenterName": "Exchange Admin",
"comment": "123456",
"commentTarget": {
"id": 703,
"targetId": 216,
"targetName": "TICKET",
"created": 1586548428358,
"updated": 1586548428358,
"comments": [
{
"id": 997,
"commentedBy": 1,
"commenterName": "Exchange Admin",
"comment": "123456",
"commentTarget": {
"id": 703,
"targetId": 216,
"targetName": "TICKET",
"created": 1586548428358,
"updated": 1586548428358,
"comments": [
Not sure how to separate the relationship between the two when just wanting to return the child list of comments.

Note that:
#JsonManagedReference is the forward part of reference – the one
that gets serialized normally.
#JsonBackReference is the back part
of reference – it will be omitted from serialization.
Change your mapping like below.
#OneToMany(mappedBy = "commentTarget", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JsonManagedReference
private List<Comment> comments;
#ManyToOne
#JoinColumn(name = "comment_target_id")
#JsonBackReference
private CommentTarget commentTarget;

Related

Hibernate maps full relational object only in first occurance in the aggregation

Problem I have stumbbled upon is the relation below in my RestApi.
While requesting for all payments Object having PaymentCategory in relation is fully returned. But when PaymentCategory in the List is present for the next time, then the Payment object contains only an Id of PaymentCategory.
snippet Payment.class
#ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.EAGER)
private PaymentCategory paymentCategory;
PaymentCategory.class
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public class PaymentCategory {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
Response querying all Payments
[
{
"id": 1,
"name": "Hipoteka",
"description": "Hipoteka w banku PEKAO SA",
"paymentDate": "2022-10-11",
"lastPaymentDate": "2022-09-11",
"paymentDueDay": 11,
"amount": 13.13,
"paymentClosed": false,
"paymentClosingDate": null,
"paymentCategory": {
"id": 1,
"name": "Dom"
},
"logo": "WAPBouvlSP6gEKLEPjl/7Hmul8o=",
"repayments": []
},
{
"id": 2,
"name": "Spotify",
"description": "Spotify premium",
"paymentDate": "2022-10-25",
"lastPaymentDate": "2022-09-25",
"paymentDueDay": 25,
"amount": 29.99,
"paymentClosed": false,
"paymentClosingDate": null,
** "paymentCategory": {
"id": 2,
"name": "Rozrywka"
},**
"logo": "WAPBouvlSP6gEKLEPjl/7Hmul8o=",
"repayments": []
},
{
"id": 3,
"name": "Viaplay",
"description": "Viaplay abonament",
"paymentDate": "2022-10-11",
"lastPaymentDate": "2022-09-17",
"paymentDueDay": 17,
"amount": 34.0,
"paymentClosed": false,
"paymentClosingDate": null,
**"paymentCategory": 2,**
"logo": "WAPBouvlSP6gEKLEPjl/7Hmul8o=",
"repayments": []
}
]
What am I missing here?
Is there an additional configuration for entityManager?
Best Regards,
Mateusz
I have tried looking in some guides, but I didn't find solution I've expected.
#JsonIdentityInfo in PaymentCategory was actually limiting the object property.
There is no use in this class due to due to single directional relation.

How to avoir infinite recursion using bidirectionnal OneToMany relationships, and get the informations from both sides?

Let's say that an author have written many books, and also many books have only one author.
So that it is a bidirectional relationship.
When you try to serialize it, it becomes an infinite loop of books and authors like this :
{
"id": 1,
"name": "1984",
"author": {
"id": 1,
"name": "George Orwell",
"books": [
{
"id": 1,
"name": "1984",
"author": {
"id": 1,
"name": "George Orwell",
"books": [
{
"id": 1,
"name": "1984",
"author": {
...etc.
Those two annotations (#JsonManagedReference and #JsonBackReference) help us to break this loop :
#Entity
public class Book {
#ManyToOne
#JoinColumn(name = "author_id")
#JsonManagedReference
private Author author;
}
#Entity
public class Author extends AbstractEntity {
#OneToMany(mappedBy = "author", fetch = FetchType.LAZY,
cascade = CascadeType.ALL)
// #JsonIgnore
#JsonBackReference
private Set<Book> books;
}
But with this solution you can access to the books with their proper author
{
"id": 1,
"name": "1984",
"author": {
"id": 1,
"name": "George Orwell"
}
}
, but you can access to the authors with their books :
{
"id": 1,
"name": "George Orwell"
}
Does someone already did fix this problem ?
Or it's just the way it is to access the complete information from only one side ?
Thank you for your help
Joss

#JsonIgnore just for some specific endpoints

I have been struggling to solve this issue on my project: Is possible to use the annotation #JsonIgnore only when endpoint has an specific value?
For example, i want to use the annotation when endpoint.equals("xxxxxxxxx"), but not use when endpoint.equals("yyyyyy").
There are 3 classes with these relationship annotations:
Client
#OneToMany(mappedBy = "ownerOfTheProduct")
#JsonIgnore
private List<Product> ownProducts = new ArrayList<>();
Category
#JsonIgnore
#OneToMany(mappedBy = "category")
private List<Product> products;
Product
#ManyToOne
#JoinTable(name = "PRODUCT_CATEGORY", joinColumns = #JoinColumn(name = "product_id"), inverseJoinColumns = #JoinColumn(name = "category_id"))
private Category category;
#ManyToOne
#JoinTable(name = "CLIENT_PRODUCT", joinColumns = #JoinColumn(name = "product_id"), inverseJoinColumns = #JoinColumn(name = "client_id"))
private Client ownerOfTheProduct;
The point is:
If i dont put the #JsonIgnore, i get a StackOverflow error, the json gets into looping and wont stop.
"id": 1,
"name": "Product name",
"price": 20.0,
"category": {
"id": 1,
"name": "Cleaning",
"products": [
{
"id": 1,
"name": "Product name",
"price": 20.0,
"category": {
...
When i mapped in a different way, and put the #JsonIgnore into the both classes: Client and Product, it works, the loopings were not more hapenning. However, when i have to use other endpoint, which the fields products and ownerOfTheProduct need to show up through api, it doesnt work cuz the #JsonIgnore is annotated.
LOOPING SOLVED
{
"id": 1,
"name": "Product name",
"price": 20.0,
"category": {
"id": 1,
"name": "Cleaning"
},
"ownOfTheProduct": {
"id": 1,
"name": "Edited",
"cpf": "Edited",
"email": "test",
"password": "test"
}
}
OTHER ENDPOINTS ARE NOT WORKING
{
"id": 1,
"name": "Edited",
"cpf": "Edited",
"email": "test",
"password": "test"
}
I'd like the field that i have mapped with #JsonIgnore (ownProducts) shows up in this request exactly this way:
{
"id": 1,
"name": "Edited",
"cpf": "Edited",
"email": "test",
"password": "test"
"ownProducts" [
{
"id": 1,
"name": "Product name",
"price": 20.0,
"category": {
"id": 1,
"name": "Cleaning"
},
]
}
Is there a way to change this? Summing up, i just want to use #JsonIgnore with especific especific endpoints, not every single endpoint on my API.
I hope yall got my question, anyway here is the link of the repository on github: https://github.com/reness0/spring-restapi-ecommerce
You cant use only #JsonIgnore but you can use #JsonView and #JsonIdentityInfo annotations from com.fasterxml.jackson.core
How it works:
You need define class with interfaces. For example:
public class SomeView {
public interface id {}
public interface CoreData extends id {}
public interface FullData extends CoreData {}
}
Mark entity fields with #JsonView(<some interface.class>)
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonView(SomeView.id.class)
private Long id;
#Column(nullable = false)
#JsonView(SomeView.CoreData.class)
private String username;
#Column(nullable = false)
#JsonView(SomeView.FullData.class)
private String email;
}
Annotate endpoint with #JsonView(<some interface.class>)
#GetMapping()
#JsonView(SomeView.FullData.class)
public User getUser() {
return <get user entity somwhere>
}
In case #JsonView(SomeView.id.class) you will get this JSON:
{
id: <some id>
}
In case #JsonView(SomeView.CoreData.class):
{
id: <some id>,
username: <some username>
}
In case #JsonView(SomeView.FullData.class):
{
id: <some id>,
username: <some username>,
email: <some email>
}
#JsonView also works with embeded objects and you can annotate one field with multiply views classes - #JsonView({SomeView.FullData.class, SomeOtherView.OtherData.class})
About Cycleing JSON. Annotate your entity class with
#JsonIdentityInfo(
property = "id",
generator = ObjectIdGenerators.PropertyGenerator.class
)
Every time when JSON serialization go in circles object data will be replaced with object id or orher field of entity for your choose.
Or as alternative you can just use DTO classes
While this is not possible to achieve using the annotation based approach (annotations make it static), you can achieve the same using any data mapper library. Create a filter based on the attribute from API. Orika library can be used: https://www.baeldung.com/orika-mapping

Jackson serialization issue. Only first object of the same entity serializes well

I develop a REST voting system where users can vote on restaurants. I have a Vote class which contains User, Restaurant and Date.
public class Vote extends AbstractBaseEntity {
#NotNull
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
#NotNull
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "restaurant_id")
private Restaurant restaurant;
#Column(name = "date", nullable = false)
#NotNull
private LocalDate date;
}
I need to find all votes of the day. And if there are several votes for one restaurant, only first object serializes well. The other ones shows restaurant ID instead of Restaurant object as shown below:
[
{
"id": 100019,
"user": null,
"restaurant": {
"id": 100004,
"name": "KFC"
},
"date": "2020-08-28"
},
{
"id": 100020,
"user": null,
"restaurant": 100004,
"date": "2020-08-28"
},
{
"id": 100021,
"user": null,
"restaurant": {
"id": 100005,
"name": "Burger King"
},
"date": "2020-08-28"
},
{
"id": 100022,
"user": null,
"restaurant": 100005,
"date": "2020-08-28"
}
]
So first Vote for KFC shows full restaurant info, but second shows only ID. Same for Burger King which is next 2 votes.
What could be a problem?
You need to use com.fasterxml.jackson.annotation.JsonIdentityInfo annotation and declare it for Restaurant class:
#JsonIdentityInfo(generator = ObjectIdGenerators.None.class)
class Restaurant {
private int id;
...
}
See also:
Jackson/Hibernate, meta get methods and serialisation
Jackson JSON - Using #JsonIdentityReference to always serialise a POJO by id

I am using ManyToOne , OneToMany and have endless loop when getting data

I am using ManyToOne and OneToMany in hibernate .I want to create a user who has locations.
When I get data in postman I have endless loop because when I get user it's showing a user's location and in location showing user and so on. Here is code:
Locations class :
#ManyToOne(fetch = FetchType.LAZY,cascade = CascadeType.ALL)
#JoinColumn(name=FLD_LOC, nullable=false)
private Consumer consumers;
public Consumption(String location, float consumpiton,Consumer consumer) {
this.location = location;
this.consumpiton = consumpiton;
this.consumers=consumer;
}
User class :
#OneToMany(mappedBy = Consumption.FLD_LOC,orphanRemoval = true)
private List<Consumption> locations ;
public Consumer(String clientId, String name,String location, float pwConsumption, String email, String password, String roles) {
super(clientId, name, email, password, roles);
this.locations=new ArrayList<>();
this.location=location;
this.pwcons=pwConsumption;
}
But in database it's storing name of location in users table and id of user in locations table
Here is problem looks like :
"id": 2,
"version": 1,
"updated": "2020-06-28T15:41:49.082",
"clientId": "admin",
"name": "admin",
"email": "admin123#gmail.com",
"password": "$2a$10$hgcTSHjGpxEPg6WNb0U7ouHR5J5YYR5l1XVAejdK8JsG9w2Bko00a",
"active": true,
"roles": "ROLE_ADMIN",
"locations": [
{
"locationsid": 1,
"location": "Pecs",
"consumpiton": 0.0,
"consumers": {
"id": 2,
"version": 1,
"updated": "2020-06-28T15:41:49.082",
"clientId": "admin",
"name": "admin",
"email": "admin123#gmail.com",
"password": "$2a$10$hgcTSHjGpxEPg6WNb0U7ouHR5J5YYR5l1XVAejdK8JsG9w2Bko00a",
"active": true,
"roles": "ROLE_ADMIN",
"locations": [
{
"locationsid": 1,
"location": "Pecs",
"consumpiton": 0.0,
"consumers": {
"id": 2,
"version": 1,
"updated": "2020-06-28T15:41:49.082",
"clientId": "admin",
"name": "admin",
"email": "admin123#gmail.com",
"password": "$2a$10$hgcTSHjGpxEPg6WNb0U7ouHR5J5YYR5l1XVAejdK8JsG9w2Bko00a",
"active": true,
"roles": "ROLE_ADMIN",
"locations": [
{
"locationsid": 1,
"location": "Pecs",
How to let it show in JSON Locations part only name of location or id?
Problem
This is generic issue when you have to serialise objects with bidirectional relationship.
Solution
Signal the serialiser where to stop when facing bidirectional relationship
First approach is to create custom DTOs and return them from your rest controller. In the DTOs, you will populate the location field of the customerDto with locationDtos but you will NOT set the customer field of locationDto and it will be null.
Second approach is less preferred. But we can tell the Jackson library to not to serialise it recursively by adding #JsonManagedReference and #JsonBackReference.
Replace
#OneToMany(mappedBy = Consumption.FLD_LOC,orphanRemoval = true)
private List<Consumption> locations ;
with
#OneToMany(mappedBy = Consumption.FLD_LOC,orphanRemoval = true)
#JsonManagedReference
private List<Consumption> locations ;
Replace
#ManyToOne(fetch = FetchType.LAZY,cascade = CascadeType.ALL)
#JoinColumn(name=FLD_LOC, nullable=false)
private Consumer consumers;
with
#ManyToOne(fetch = FetchType.LAZY,cascade = CascadeType.ALL)
#JoinColumn(name=FLD_LOC, nullable=false)
#JsonBackReference
private Consumer consumers;
Note:
In production systems, we don't expose all the fields of domain objects as it can have many internal fields which should not be exposed to outside. It is the reason, first approach is preferred

Categories