Many to Many / Circular reference issue Jackson+Spring+Hibernate - json

Course.java
#Entity
#Table
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Course {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#NotEmpty
private String description;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "subject_course", joinColumns = #JoinColumn(name = "subject_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "course_id", referencedColumnName = "id"))
private Set<Subject> subjects = new HashSet<Subject>();
---- getter/setter ----
Subject.java
#Entity
#Table
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Subject {
#Id
#Column
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String description;
#ManyToMany(mappedBy = "subjects", fetch=FetchType.EAGER)
#Cascade({CascadeType.DELETE, CascadeType.SAVE_UPDATE})
private Set<Course> courses = new HashSet<Course>();
---- getter/setter ----
Request configuration in Spring:
#RequestMapping(value = "/courses", method = RequestMethod.GET)
public #ResponseBody ResponseEntity<?> getAllCourses() {
List<Course> courses = courseService.getAllCourses();
if (courses.isEmpty()) {
return new ResponseEntity<Message>(new Message("error", "No course found!"), HttpStatus.NOT_FOUND);
}
return new ResponseEntity<List<Course>>(courses, HttpStatus.OK);
}
Hibernate Version: 4.2.0.Final
Spring Version: 3.2.8.RELEASE
Jackson:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.2.4</version>
</dependency>
Expecting O/P
[{
"id": 1,
"description": "BCA",
"subjects":[{
"id":1,
"description":"Physics",
"courses":[1,2,3] //Either show blank array or only ids
}]
},{
"id": 2,
"description": "BSC",
"subjects":[{
"id":1,
"description":"Physics",
"courses":[1,2,3]
}]
},{
"id": 3,
"description": "BA",
"subjects":[{
"id":1,
"description":"Physics",
"courses":[1,2,3]
}]
},]
But getting O/P:
[
{
"id": 1,
"description": "BCA",
"subjects": [
{
"id": 1,
"description": "Math",
"staffs": [],
"courses": [
{
"id": 4,
"description": "BDA",
"subjects": [
1
],
"students": []
},
{
"id": 3,
"description": "BBA",
"subjects": [
1
],
"students": []
},
1
],
"students": []
}
],
"students": [
{
"id": 1,
"name": "",
"age": 0,
"gender": null,
"course": 1,
"subjects": []
}
]
},
3,
4
]
As per actual o/p, it is stopping the recursion at second level. But my requirement is not to repeat the same objects data from child... It means Course must not repeat its data in Subject's course property. Similarly, if call the same from Subject then subject should not repeat Course subject property value. It is better to skip, if can't then just display id values separated by comma.
Please advise how to fix this issue.

You can use #JsonIgnore in the Subject class like this:
#ManyToMany(mappedBy = "subjects", fetch=FetchType.EAGER)
#Cascade({CascadeType.DELETE, CascadeType.SAVE_UPDATE})
#JsonIgnore
private Set<Course> courses = new HashSet<Course>();

Related

Create a model and insert value with a #Query

i have this class:
#Getter
#Setter
#ToString
#AllArgsConstructor
#NoArgsConstructor
public class ViewOrderRTO {
private List<LineProductModel> products;
private OrderModel order;
}
that i use for display data saved in db. LineProductModel is a class that has these field: total price, quantity,id, a #ManyToOne relationship with productModel and a #Many to one relationship with OrderModel.
The question is: can i use #Query in something like this "SELECT new alongpath.ViewOrderRTO(....) ..." and insert a list of lineProduct and the other fields all in one query? or i have to split the work and get all the single LineProductModel and put in a list first and then create the ViewModel?
btw is a spring boot project and i'm using mysql
I "solved" the problem removing the #ManyToOne mapping inside the LineProductModel and adding the #OneToMany mapping in the Order Model now i have this:
#Entity
#Table(name = "order_commission")
#Getter
#Setter
#ToString
#AllArgsConstructor
#NoArgsConstructor
public class OrderModel {
#Id
#SequenceGenerator(name = "order_sequence",
sequenceName = "order_sequence",
initialValue = 0,
allocationSize = 1
)
#GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "order_sequence")
private Long id;
#OneToMany(cascade = CascadeType.ALL,
fetch = FetchType.EAGER,
orphanRemoval = true)
private List<LineProductModel> linePoducts;
private BigDecimal total;
private LocalDateTime orderedAt;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "fk_user")
private UserModel user;
public OrderModel(List<LineProductModel> lineProducts,
BigDecimal total,
LocalDateTime orderedAt,
UserModel user) {
this.linePoducts = lineProducts;
this.total = total;
this.orderedAt = orderedAt;
this.user = user;
}
}
and this is the json i get while performing a #GetMapping:
{
"id": 0,
"linePoducts": [
{
"id": 0,
"quantity": 2,
"lineProductPrice": 6,
"product": {
"id": 0,
"eanCode": "1111111111111",
"name": "pipo",
"price": 3,
"quantity": 8,
"weight": 10,
"description": "test",
"category": {
"id": 0,
"name": "cibo"
}
}
},
{
"id": 1,
"quantity": 7,
"lineProductPrice": 21,
"product": {
"id": 1,
"eanCode": "2111111111111",
"name": "pipobevanda",
"price": 3,
"quantity": 3,
"weight": 10,
"description": "test",
"category": {
"id": 1,
"name": "bevanda"
}
}
},
{
"id": 2,
"quantity": 1,
"lineProductPrice": 3,
"product": {
"id": 2,
"eanCode": "3111111111111",
"name": "pipoverdura",
"price": 3,
"quantity": 9,
"weight": 10,
"description": "test",
"category": {
"id": 2,
"name": "verdura"
}
}
}
],
"total": 0,
"orderedAt": "2022-08-10T20:48:15",
"user": {
"id": 0,
"firstName": "-------",
"lastName": "------",
"email": "--------",
"password": "------",
"countryOfResidence": "italy",
"birthdate": "-------",
"balance": 100
}
}
i have to make some changes about the information that displays, but is there a better solution or this one is ok?

How to return the inner fields at same level in Spring Boot?

I want to return the class at the same level here.
As here id, username, password, etc are under userCredentialEntity and so in userDetailEntity key.
{
"userCredentialsEntity": {
"id": 5,
"username": "testuser3",
"password": "$2a$10$yFmeUcE3uTOf9H4TZqWXfO/b8zsTp6sqnWax5iyRXBhlfXF3dSsk2",
"email": "testuser3#gmail.com",
"roles": [
{
"id": 2,
"name": "ROLE_MANAGER"
}
]
},
"userDetailsEntity": {
"userId": 5,
"first_name": "Test",
"last_name": "Singh 3",
"birth_date": "12",
"birth_month": "01",
"birth_year": "2002",
"area": "Chandani Chowk",
"city": "Sahadra",
"district": "Sonbhadra",
"pin_code": 231325,
"mobile_number": "6788762345"
}
}
I have to return this.
{
"id": 5,
"username": "testuser3",
"email": "testuser3#gmail.com",
"roles": [
{
"id": 2,
"name": "ROLE_MANAGER"
}
],
"first_name": "Test",
"last_name": "Singh 3",
"birth_date": "12",
"birth_month": "01",
"birth_year": "2002",
"area": "Chandani Chowk",
"city": "Sahadra",
"district": "Sonbhadra",
"pin_code": 231325,
"mobile_number": "6788762345"
}
And also neglect the password and userId field while returning
The Code for my implementation is:
#Data
public class UserMerged {
private UserCredentialsEntity userCredentialsEntity;
private UserDetailsEntity userDetailsEntity;
}
UserDetailsEntity userDetailsEntity = userDetailService.fetchUserDetails(userId).get();
UserCredentialsEntity userCredentialsEntity = userCredentialsRepository.findByUsername(username);
UserMerged userMerged = new UserMerged();
userMerged.setUserDetailsEntity(userDetailsEntity);
userMerged.setUserCredentialsEntity(userCredentialsEntity);
return ResponseEntity.ok().body(userMerged);
I am new in Spring Boot and doesn't got any satisfactory answer regarding this. Please suggest me any way of achieving this.
Thank You.
First thing first, Whatever your return type of object is, for that you need to create the specific class and then return it.
In your case Modify your UserMerged class with whatever u would like to return and then set the parameters accordingly.
Likely,
Your UserMerged will be
#Data
public class UserMerged {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer Id;
#Column(name="username")
private String username;
#Column(name="email")
private String email;
#Column(name="roles")
private List<Roles> roles;
#Column(name="first_name")
private String first_name;
#Column(name="last_name")
private String last_name;
#Column(name="birth_date")
private String birth_date;
#Column(name="birth_month")
private String birth_month;
#Column(name="birth_year")
private String birth_year;
#Column(name="area")
private String area;
#Column(name="city")
private String city;
#Column(name="district")
private String district;
#Column(name="pin_code")
private Integer pin_code;
#Column(name="mobile_number")
private Integer mobile_number;
And instead of setting the UserDetailsEntity and UserCredentialsEntity you can set your fields from those two classes to UserMerged class.
userMerged.setId(userCredentialsEntity.getId());
userMerged.setUsername(userCredentialsEntity.getUsername());
userMerged.setEmail(userCredentialsEntity.getEmail());
userMerged.setRoles(userCredentialsEntity.getRoles());
userMerged.setFirst_name(userDetailsEntity.getFirst_name());
userMerged.setLast_name(userDetailsEntity.getLast_name());
userMerged.setBirth_date(userDetailsEntity.getBirth_date());
userMerged.setBirth_month(userDetailsEntity.getBirth_month());
userMerged.setBirth_year(userDetailsEntity.getBirth_year());
userMerged.setArea(userDetailsEntity.getArea());
userMerged.setCity(userDetailsEntity.getCity());
userMerged.setDistrict(userDetailsEntity.getDistrict());
userMerged.setPin_code(userDetailsEntity.getPin_code());
userMerged.setMobile_number(userDetailsEntity.getMobile_number());
This will set all parameters you needed, and you can return the object of user merged object.
Here Their is lot much repetation of code exist so instead so please take care of that or you can also add common parameters into one class.

How to get relationship data with spring boot rest api?

I'm building a REST API with Spring Boot to retrieve boat information. I'm using Spring Data Rest and Spring Data JPA. When I get the data from the API, I don't know why the relationship data are not with the others informations.
Do I have to configure something in Spring to get the relationship with my data ?
Here is my file.
Boat entity:
#Entity
#Table(name="boat")
#Data
public class Boat {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#Column(name = "description")
private String description;
#ManyToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "type_id", nullable = false)
#JsonBackReference
private BoatType type;
}
Boat type entity :
#Entity
#Table(name = "boat_type")
#Data
public class BoatType {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "type")
#JsonManagedReference
private Set<Boat> boats;
}
Boat repository :
#CrossOrigin("http://localhost:4200")
public interface BoatRepository extends JpaRepository<Boat, Long> {
}
JSON response :
{
"_embedded": {
"boats": [
{
"id": 1,
"name": "Boat 1",
"description": "A brief description of the boat 1",
"_links": {
"self": {
"href": "http://localhost:8080/api/boats/1"
},
"boat": {
"href": "http://localhost:8080/api/boats/1"
},
"type": {
"href": "http://localhost:8080/api/boats/1/type"
}
}
},
...
]
}
Result expected (with the type object too) :
{
"_embedded": {
"boats": [
{
"id": 1,
"name": "Boat 1",
"description": "A brief description of the boat 1",
"type": {
"id": 1,
"name": "Motorboats"
},
"_links": {
"self": {
"href": "http://localhost:8080/api/boats/1"
},
"boat": {
"href": "http://localhost:8080/api/boats/1"
},
"type": {
"href": "http://localhost:8080/api/boats/1/type"
}
}
},
...
]
}
I think that the problem is related with Spring Data Rest because when i do the same app with my own controller and repository, i get the data I need.
Is there a way to "configure" spring data rest?
It seems like you've used #JsonBackReference and #JsonManagedReference the other way around, than you needed. You've put #JsonBackReference on the type field in your Boat class, whereas its documentation states:
[...] Linkage is handled such that the property annotated with this annotation is not serialized
So it seems like you need to put #JsonManagedReference annotation on it instead (see: JsonManagedReference documentation) and put #JsonBackReference on boats in your BoatType class.
Alternatively, you could consider using #JsonIdentityInfo instead. See: the documentation.
Also, this article might be helpful. It explains various ways to handle bidirectional relationships using Jackson.
Change #JsonManagedReference and #JsonBackReference to #JsonIgnoreProperties.
In your case:
#OneToMany(cascade = CascadeType.ALL, mappedBy = "type")
#JsonIgnoreProperties(value = {"type"})
private Set<Boat> boats;
and
#ManyToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "type_id", nullable = false)
#JsonIgnoreProperties(value = {"boats"})
private BoatType type;
You will avoid the infinity loop in json result and get all reference objects (relationships).
The Boat response includes a uri to your BoatType resource by default since you defined a rest repository for your BoatType resource (docs)
To override this behaviour, define a projection to expose the boat type data (docs):
#Projection(name = "boatDetail", types = { Boat.class })
interface BoatDetail {
// ... all other fields you want included in the response
BoatType getType();
}
Then include the projection as a query parameter:
{apiUrl}boats/1?projection=boatDetail
The response should now include the boat type data:
{
"id": 1,
"name": "Boat 1",
"description": "A brief description of the boat 1",
"type": {
"id": 1,
"name": "Motorboats"
},
"_links": {
// ...
}
}
To automatically include the projection on a collection of resources, use an excerpt (docs):
#RepositoryRestResource(excerptProjection = BoatDetail.class)
interface BoatRepository extends JpaRepository<Boat, Long> {}
Then the http response:
{
"_embedded":{
"boats":[
{
"id":1,
"name":"Boat 1",
"description":"A brief description of the boat 1",
"type":{
"id":1,
"name":"Motorboats"
},
"_links":{
//...
}
},
// ...
]
}
}

JPA Circular dependency on same entity resulting in recursive output

I am having a Folder class which in-turn can have many sub-folders. In this case i am trying to make use of same entity that single entity is being used to achieve the same.
Something like below.
Folders
----SubFolder
----SubFolder
Below are the classes.
FolderData.java
#Entity
#JsonIdentityInfo(generator =ObjectIdGenerators.IntSequenceGenerator.class,property="projectId")
public class FolderData {
#Id
#SequenceGenerator(name = "seq-gen", initialValue = 1)
#GeneratedValue(strategy = GenerationType.IDENTITY, generator = "seq-gen")
private Integer parentId;
private int moduleId;
private int subProjectId;
private String folderName;
private Integer folderId;
private int projectId;
#ManyToOne(fetch = FetchType.LAZY,cascade=CascadeType.ALL)
#JoinColumn(name="folder_child")
#JsonIgnore
private FolderData folderData;
#OneToMany(mappedBy = "folderData")
#JsonIgnoreProperties("folderList")
private Set<FolderData> folderList=new HashSet<>();
}
FodlerController.java
#RestController
#RequestMapping("/folder")
public class FodlerController {
#Autowired
private FolderService folderService;
#GetMapping(produces = "application/json")
public List<FolderData> getFolderList(){
return folderService.findAllFromTheList();
}
#PostMapping(produces = "application/json", consumes = "application/json")
public void createFolder(#RequestBody FolderData folderData) {
if(folderData.getId()==null && folderData.getFolderId()==null) {
System.out.println("id is null");
folderData.setFolderId(new Random().nextInt());
folderService.save(folderData);
}
else {
folderService.doChildAddition(folderData);
}
}
}
FolderService.java
#Service
public class FolderService {
#Autowired
private FolderRepo folderRepo;
public FolderData save(FolderData folderData) {
return folderRepo.save(folderData);
}
public FolderData getFolderDataByParentId(Integer id) {
return folderRepo.getOne(id);
}
public List<FolderData> findAllFromTheList() {
return folderRepo.findAll();
}
public FolderData getFolderDataByfolderId(Integer folderId) {
return folderRepo.findFolderByFolderId(folderId);
}
public void doChildAddition(FolderData childFolder) {
FolderData parentFolder=folderRepo.findFolderByFolderId(childFolder.getFolderId());
childFolder.setFolderData(parentFolder);
FolderData childFolderSaved = folderRepo.saveAndFlush(childFolder);
//parentFolder.getFolderList().add(folderRepo.getOne(childFolderSaved.getId()));
//folderRepo.save(parentFolder);
}
}
Json Request for creating parent and the response collected which includes autogenerated folder id:
{
"moduleId":1,
"subProjectId":1,
"folderName":"One",
"projectId":1
}
[
{
"projectId": 0,
"moduleId": 1,
"subProjectId": 1,
"folderName": "One",
"folderId": 963031296,
"folderList": [],
"id": 1
}
]
Creating child for the parent and the response collected.
{
"moduleId":1,
"subProjectId":2,
"folderName":"Two",
"projectId":1,
"folderId": -963031296
}
[
{
"projectId": 0,
"moduleId": 1,
"subProjectId": 1,
"folderName": "One",
"folderId": 963031296,
"folderList": [
{
"projectId": 0,
"moduleId": 1,
"subProjectId": 2,
"folderName": "Two",
"folderId": 963031296,
"id": 2
}
],
"id": 1
},
2
]
For the above response i am getting the No 2 with the response since i am using
#JsonIdentityInfo(generator =ObjectIdGenerators.IntSequenceGenerator.class,property="parentId")
else the whole object would have come instead of 2.
Example output had i not used JsonIdentityInfo
[
{
"projectId": 0,
"moduleId": 1,
"subProjectId": 1,
"folderName": "One",
"folderId": 963031296,
"folderList": [
{
"projectId": 0,
"moduleId": 1,
"subProjectId": 2,
"folderName": "Two",
"folderId": 963031296,
"id": 2
}
],
"id": 1
},
{
"projectId": 0,
"moduleId": 1,
"subProjectId": 2,
"folderName": "Two",
"folderId": 963031296,
"id": 2
}
]
It's easy, #Prash
Since you have used a Set in a undirectional dependency you must supply an uniqueness of a joined (depended) entities. Any set is using hashCode() and equals() to determine an availability of the concurent item that is want to be added. Your FolderData class doesn't have hashCode() and equals() so from the point of view of JVM all the deserialized objects are same and because of the Set nature it's normal to keep only last example

Hide Object reference in JSON serialization by Jackson when using #JsonIdentityInfo

I have two entities: Category and Item.
Category Entity:
#JsonInclude(Include.NON_EMPTY)
#JsonIdentityInfo(generator = ObjectIdGenerators.UUIDGenerator.class)
#Entity
public class Category {
#Id
#GeneratedValue
private int id;
private String categoryName;
#OneToMany(mappedBy = "category")
private List<Item> itemList;
//have getters and setters
}
Item Entity:
#JsonIdentityInfo(generator = ObjectIdGenerators.UUIDGenerator.class)
#Entity
public class Item {
#Id
#GeneratedValue
private int id;
private String itemName;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "fk_Category_id")
private Category category;
//have getters and setters
}
After doing a left join query like this:
"SELECT c from Category c Left JOIN FETCH c.itemList il"
I got the result and then I have done hibernate aware JSON serialization to the result.
My HibernateAwareObjectMapper is:
public class HibernateAwareObjectMapper extends ObjectMapper {
public HibernateAwareObjectMapper() {
Hibernate4Module hibernateModule = new Hibernate4Module();
hibernateModule.disable(Hibernate4Module.Feature.FORCE_LAZY_LOADING);
registerModule(hibernateModule);
}
}
And HibernateAwareSerializerFactory is:
public class HibernateAwareSerializerFactory extends BeanSerializerFactory {
protected HibernateAwareSerializerFactory(SerializerFactoryConfig config) {
super(config);
}
}
In my dispatcher servlet I have written:
<mvc:annotation-driven>
<mvc:message-converters>
<bean
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="org.scinv.hibernateAwareMapper.HibernateAwareObjectMapper" />
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
So, I have got a JSON Like this:
{
"ArrayList": [
{
"#id": "971ef69e-1605-46f2-8234-b595e38be11a",
"id": 1,
"categoryName": "fruit",
"itemList": [
{
"#id": "75a3a7e5-ce66-4f6d-a04c-d04145d92b21",
"id": 1,
"itemName": "mango",
"category": "971ef69e-1605-46f2-8234-b595e38be11a"
},
{
"#id": "0aa0fb71-2909-4909-8403-0765829ee8c1",
"id": 2,
"itemName": "apple",
"category": "971ef69e-1605-46f2-8234-b595e38be11a"
},
{
"#id": "02c381cb-33fa-45a6-bff9-ec146357f4bc",
"id": 3,
"itemName": "orange",
"category": "971ef69e-1605-46f2-8234-b595e38be11a"
}
]
},
"971ef69e-1605-46f2-8234-b595e38be11a",
"971ef69e-1605-46f2-8234-b595e38be11a"
]
}
In this JSON,
there is "#id": "971ef69e-1605-46f2-8234-b595e38be11a"
which is Object Id for Category Entity.
And there is "#id": "75a3a7e5-ce66-4f6d-a04c-d04145d92b21"
which is Object Id for Item Entity.
Category has 3 items So Catagory's object Id is also showing 2 more times.
Like this: "971ef69e-1605-46f2-8234-b595e38be11a","971ef69e-1605-46f2-8234-b595e38be11a"
That Generated JSON serialization is as expected.
But I just need to hide that Object reference "#id" in that json. This "#id" is by default created by #JsonIdentityInfo.
So I need configuration for #JsonIdentityInfo to hide Object Id "#id".
I have tried #JsonIgnore but failed.
I have googled 2 days for solution but failed.Can anyone give me a solution or a suggestion?