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

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.

Related

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

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

Child Object has parent Object as attribute JPA caused endless JSON

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;

Spring JPA Repository ony fetches id instead of full object when it's already in result

In a SpringBoot rest application, I have two classes as follows:
User.java
and Message.java.
Message has -from- field (User) and also -to- is of type (User).
So I've made it like this:
In User.java:
#Entity
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class,
property="id")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String firstName;
private String lastName;
private String email;
#JsonIgnore
#OneToMany(mappedBy = "to")
private List<Message> receivedMessages;
#OneToOne
#JoinColumn(name = "type")
private UserType type;
In Message.java:
#Entity
public class Message {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Integer id;
#ManyToOne
#JoinColumn(name = "from_user_id")
private User from;
#ManyToOne
#JoinColumn(name = "to_user_id")
private User to;
private String subject;
private String message;
private Date sentTime;
private Date readTime;
private Integer replyTo;
(setters & getters, etc)
And apparently it works!
-BUT- let's say I have 3 messages, and the first two of them went sent to the same user, only the first of those two comes with the full user object and the seconds only it's id, as follows:
[
{
"id": 16,
"from": {
"id": 1,
"firstName": "Ale",
"lastName": null,
"email": "axfeea#gmail.com",
"username": null,
"password": "123456",
"avatar": "https://..............jpg",
"type": null
},
"to": 1,
"subject": "sub",
"message": "hola",
"sentTime": null,
"readTime": null,
"replyTo": null
},
{
"id": 17,
"from": {
"id": 2,
"firstName": "Carlos",
"lastName": "Perez",
"email": "efefe#fefe.com",
"username": null,
"password": "fe",
"avatar": "https://..................jpg",
"type": null
},
"to": 1,
"subject": "sub1",
"message": "chau",
"sentTime": null,
"readTime": null,
"replyTo": null
},
{
"id": 18,
"from": 2,
"to": 1,
"subject": "efefae",
"message": "oooook",
"sentTime": 1503249653000,
"readTime": null,
"replyTo": null
}
]
And if 3rd message comes with a non-repeated user it comes with the full object.
I need the full object to come always.
And -btw- in the database they all look good and same way.
Any ideas?
Thank you all in advance!
Since you have specified the annotation JsonIdentityInfo, Jackson serializes the objects as in the resulting JSON.
The Javadoc of the annotation specifies:
In practice this is done by serializing the first instance as full object and object identity, and other references to the object as reference values.
So if you don't want that behaviour, remove the annotation.

Spring boot JPA - JSON without nested object with OneToMany relation

I have a project which deals with some ORM mapping of objects (there are some #OneToMany relations etc).
I am using REST interface to treat these objects and Spring JPA to manage them in the API.
This is an example of one of my POJOs:
#Entity
public class Flight {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
private String dateOfDeparture;
private double distance;
private double price;
private int seats;
#ManyToOne(fetch = FetchType.EAGER)
private Destination fromDestination;
#ManyToOne(fetch = FetchType.EAGER)
private Destination toDestination;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "flight")
private List<Reservation> reservations;
}
When making a request, I have to specify everything in the JSON:
{
"id": 0,
"reservations": [
{}
],
"name": "string",
"dateOfDeparture": "string",
"distance": 0,
"price": 0,
"seats": 0,
"from": {
"id": 0,
"name": "string"
},
"to": {
"id": 0,
"name": "string"
}
}
What I would prefer, is actually specifying the id of referenced object instead of their whole bodies, like this:
{
"id": 0,
"reservations": [
{}
],
"name": "string",
"dateOfDeparture": "string",
"distance": 0,
"price": 0,
"seats": 0,
"from": 1,
"to": 2
}
Is that even possible? Could someone give me some insight on how to do this? I am only finding tutorials on how to do the opposite (the solution I already have).
Yes, it is possible.
For this purpose you should use pair of Jackson annotations to your entity model:
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
#JsonIdentityReference(alwaysAsId = true)
protected Location from;
Your serialized JSON will look instead of this:
{
"from": {
"id": 3,
"description": "New-York"
}
}
like this:
{
"from": 3
}
As mentioned in official documentation:
#JsonIdentityReference - optional annotation that can be used for
customizing details of a reference to Objects for which "Object
Identity" is enabled (see JsonIdentityInfo)
alwaysAsId = true used as marker to indicate whether all referenced
values are to be serialized as ids (true);
Note that if value of 'true' is used, deserialization may require additional contextual information, and possibly using a custom id
resolver - the default handling may not be sufficient.
You can only ignore your JSON content using #JsonIgnore annotation.
The field which you want to hide in your JSON at there you can annotate that with #JsonIgnore.
You can change your JSON like this :
{
"id": 0,
"reservations": [
{}
],
"name": "string",
"dateOfDeparture": "string",
"distance": 0,
"price": 0,
"seats": 0,
"from": {
"id": 0
},
"to": {
"id": 0
}
}
But You can't like this:
{
"id": 0,
"reservations": [
{}
],
"name": "string",
"dateOfDeparture": "string",
"distance": 0,
"price": 0,
"seats": 0,
"from": 0,
"to": 1
}

Categories