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

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

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.

SpringBoot JPA relationsships - prevent creating new entity if object existing and instead return the object that already exists

I have a Customer entity built with a controller, service and repository logic.
And i also have an Address entity with nothing more than a simple POJO and #oneToMany annotation.
If i make two POST requests with identical address properties - how do i tell my application that if these address properties already exist in the database - prevent adding a new entity in the database but instead return the already existing one?
E.g. POST requests:
// First request
{
"name": "Foo",
"contact": "123456",
"email": "Foo#gmail.com",
"address": {
"street": "Wall street",
"postalCode": 10,
"houseNo": "45",
"city": "New York"
}
}
// Second request
{
"name": "Bar",
"contact": "5321",
"email": "Bar#gmail.com",
"address": {
"street": "Wall street", <----- same as first request
"postalCode": 10, <----- same as first request
"houseNo": "45", <----- same as first request
"city": "New York" <----- same as first request
}
}
This is the result when fetching all customers:
[
{
"id": 1,
"name": "Foo",
"contact": "123456",
"email": "Foo#gmail.com",
"address": {
"id": 1,
"street": "Wall street",
"postalCode": 10,
"houseNo": "45",
"city": "New York"
}
},
{
"id": 2,
"name": "Bar",
"contact": "5321",
"email": "Bar#gmail.com",
"address": {
"id": 2, <---------------- expect this ID to be 1
"street": "Wall street",
"postalCode": 10,
"houseNo": "45",
"city": "New York"
}
}
]
Here is the necessary information (if) needed to help me proceed with my little project.
Customer.java
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
#Entity
#Table
public class Customer {
#Id
#SequenceGenerator(
name = "customer_sequence",
sequenceName = "customer_sequence",
allocationSize = 1
)
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "customer_sequence"
)
private Long id;
private String name;
private String contact;
private String email;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "address_id", nullable = false)
private Address address;
[...]
Address.java
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
#Entity
#Table
public class Address {
#Id
#SequenceGenerator(
name = "address_sequence",
sequenceName = "address_sequence",
allocationSize = 1
)
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "address_sequence"
)
private Long id;
private String street;
private int postalCode;
private String houseNo;
private String city;
#JsonIgnore
#OneToMany(mappedBy = "address")
private Set<Customer> customers;
[...]
CustomerController.java
//...
#PostMapping
public void createCustomer(#RequestBody Customer customer) {
customerService.createCustomer(customer);
}
[...]
And the service that saves the customer to the DB:
//...
public void createCustomer(Customer customer) {
customerRepository.save(customer);
}
[...]
You can create a hash with your address parameters and keep that like;
"street": "Wall street",
"postalCode": 10,
"houseNo": "45",
"city": "New York"
"uid": "3rn3u924917vt8r78b17vxver"
When you get a new address you can create a hash with it and get the address with that hash. If any address exists with that hash, get that address and put it in your customer object and save.
So I managed to NOT create duplicated addresses by doing some small changes.
First og foremost I autogenerated equals() inside my Address class:
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Address address = (Address) o;
return postalCode == address.postalCode && street.equals(address.street) && houseNo.equals(address.houseNo) && city.equals(address.city);
}
In my service where i create the customer I've implemented this code to check if any customer has the the same address as the new customer:
public void createCustomer(Customer customer) {
Optional<Customer> customerWithExistingAddress = customerRepository.findAll()
.stream()
.filter(x -> x.getAddress().equals(customer.getAddress()))
.findFirst();
customerWithExistingAddress.ifPresent(c -> customer.setAddress(c.getAddress()));
customerRepository.save(customer);
}
Now it doesn't create a new entity if already exists.

#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

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.

Categories