#ManyToOne relations missing from JSON result - java

I have just started using Spring boot and I am using the default repository api to retrieve db data as json.
I have added a #ManyToOne relation to my Song and Artist Entity.
But now i am not getting the Artist object in my json response from the server and its not really clear to me, how I can include it without missing out on the pagination functions from the PagingAndSorting repository.
I am using the spring-data-rest-jpa.
My response now looks like:
"_embedded": {
"songs": [
{
"id": 1,
"title": "SongTitle",
"genre": "Rap",
"length": 500,
"_links": {
"self": {
"href": "http://localhost:8080/api/songs/1"
},
"song": {
"href": "http://localhost:8080/api/songs/1"
},
"artist": {
"href": "http://localhost:8080/api/songs/1/artist"
}
}
}
]
},
"_links": {
"first": {
"href": "http://localhost:8080/api/songs?page=0&size=1"
},
"self": {
"href": "http://localhost:8080/api/songs?size=1"
},
"next": {
"href": "http://localhost:8080/api/songs?page=1&size=1"
},
"last": {
"href": "http://localhost:8080/api/songs?page=19&size=1"
},
"profile": {
"href": "http://localhost:8080/api/profile/songs"
}
},
"page": {
"size": 1,
"totalElements": 20,
"totalPages": 20,
"number": 0
}
}
But I want it rather to be like this:
"_embedded": {
"songs": [
{
"id": 1,
"title": "SongTitle",
"genre": "Rap",
"length": 500,
"artist": {
"id": 1,
"name": "Artistname"
}
"_links": {
"self": {
"href": "http://localhost:8080/api/songs/1"
},
"song": {
"href": "http://localhost:8080/api/songs/1"
},
"artist": {
"href": "http://localhost:8080/api/songs/1/artist"
}
}
}
]
},
"_links": {
"first": {
"href": "http://localhost:8080/api/songs?page=0&size=1"
},
"self": {
"href": "http://localhost:8080/api/songs?size=1"
},
"next": {
"href": "http://localhost:8080/api/songs?page=1&size=1"
},
"last": {
"href": "http://localhost:8080/api/songs?page=19&size=1"
},
"profile": {
"href": "http://localhost:8080/api/profile/songs"
}
},
"page": {
"size": 1,
"totalElements": 20,
"totalPages": 20,
"number": 0
}
}
Song.java
#Getter
#Setter
#Entity
#Table(name = "song")
public class Song {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false, unique = true)
private Long id;
#NotNull
#NotBlank(message = "The song has to have a title")
private String title;
#NotNull
#NotBlank(message = "The song has to have a genre")
private String genre;
#NotNull
#Min(value = 1, message = "The song has to have a song length in seconds")
private int length;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "artist_id", referencedColumnName = "id")
private Artist artist;
/* #Version
private long version;*/
public Song() {
}
public Song(String title, Artist artist, String genre, int length) {
this.title = title;
this.artist = artist;
this.genre = genre;
this.length = length;
}
public void setArtist(Artist artist) {
this.artist = artist;
}
public Artist getArtist() {
return artist;
}
}
Artist.java
#Getter
#Setter
#Entity
#Table(name = "artist")
public class Artist {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Long id;
#NotNull
#NotBlank(message = "The artist has to have a name")
private String name;
#JsonIgnore
#OneToMany(mappedBy = "artist")
private List<Song> songs;
public Artist() {
}
public Artist(String name) {
this.name = name;
}
For the sake of testing I wrote a method in my SongController:
#GetMapping
List<Song> getSongs() {
return songRepository.findAll();
}
The result includes the Artist object, but won't have any pagination to it. How could I include it?
Json Result:
[
{
"id": 1,
"title": "SongTitle",
"genre": "Rap",
"length": 500,
"artist": {
"id": 1,
"name": "ArtistName"
}
}
]

After all your useful suggestions I have found an answer:
I have changed the return type of my Method in my controller to Page and made use of the PageRequest Class which looks like this:
#GetMapping
public Page<Song> getSongs(#RequestParam(defaultValue = "0") int page, #RequestParam(defaultValue = "5") int size) {
PageRequest pr = PageRequest.of(page, size);
return songRepository.findAll(pr);
}
Also used some defaultvalues to avoid some exceptions ;)

Use either #JsonIdentityInfo or #JsonIgnore and remove #JsonBackReference. Here is the example with #JsonIgnore
public class Artist {
public Long id;
public String name;
public List<Song> songs;
}
public class Song {
public Long id;
public String title;
public String genre;
public int length;
#JsonIgnore
public Artist artist;
}
#JsonManagedReference or #JsonBackReference won't help in this case (read more on https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion):
(...) we can use the #JsonIgnore annotation to simply ignore one of the sides of the relationship, thus breaking the chain. [my addition: the chain of recurrent calls]
Here is the example with #JsonIdentityInfo:
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Artist { ... }
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Song { ... }

Related

Spring Data Mongodb Aggregation - Group by nested objects and build DTO

I have the following Employee data in MongoDB
{
"_id": {
"$oid": "625f09bb1a96bf42ff4c4006"
},
"employeeId": 1234,
"email": "jason#acme.com",
"firstName": "Jason",
"lastName": "Stuart",
"currentCTC": 1201117.61,
"department": {
"$ref": "department",
"$id": {
"$oid": "625f09bb1a96bf42ff4c4005"
}
}
}
{
"_id": {
"$oid": "625f09bb1a96bf42ff4c4006"
},
"employeeId": 1235,
"email": "jasons#acme.com",
"firstName": "Jasons",
"lastName": "Stuarts",
"currentCTC": 1201117.61,
"department": {
"$ref": "department",
"$id": {
"$oid": "625f09bb1a96bf42ff4c4005"
}
}
}
My Spring #Document looks like this:
// Employee.java
#Data
#Document
public class Employee {
#Id
private String id;
private Long employeeId;
private String email;
private String firstName;
private String middleName;
private String lastName;
private Gender gender;
private double currentCTC;
#DBRef
private Department department;
}
// Department.java
#Document
#Data
public class Department {
#Id
private String id;
private String name;
}
Now, my requirement is to find the sum of salaries Department-wise.. I need the data to be in the following way:
[
{
"department": {
"id": "625f09bb1a96bf42ff4c4006",
"name": "Engineering"
},
"cost": 31894773.01
},
{
"department": {
"id": "625f09bb1a96bf42ff4c4006",
"name": "Marketing"
},
"cost": 4552325.25
}
]
I created an aggregate function like this in Spring Data:
public List<DepartmentCost> getDepartmentCosting() {
GroupOperation groupByDepartment = group("department").sum("currentCTC").as("cost").first("$$ROOT").as("department");
Aggregation aggregation = Aggregation.newAggregation(groupByDepartment);
AggregationResults<DepartmentCost> results = mongoTemplate.aggregate(aggregation, "employee", DepartmentCost.class);
return results.getMappedResults();
}
And my expected DepartmentCost.java
#Data
#Document
public class DepartmentCost {
#DBRef
private Department department;
private double cost;
}
Now when I try this API out, I get the data correctly, but I do not get department name. It comes as null. I get a response like
[
{
"department": {
"id": "625f09bb1a96bf42ff4c4006",
"name": null,
},
"cost": 2241117.6100000003
},
{
"department": {
"id": "625f09bb1a96bf42ff4c400a",
"name": null,
},
"cost": 14774021.43
},
{
"department": {
"id": "625f09bc1a96bf42ff4c4013",
"name": null,
},
"cost": 14879633.97
}
]
How can I get the department details expanded in my model? Please help..
After a couple of attempts, I figured it out. All I had to do was this:
GroupOperation groupByDepartment = group("department").sum("currentCTC").as("cost").first("$department").as("department");
as opposed to:
GroupOperation groupByDepartment = group("department").sum("currentCTC").as("cost").first("$$ROOT").as("department");

How to set parameter local date in JSON

I am creating program "Ad catalog" that executes CRUD operations. I need to run method save(Ad ad), Ad includes parameter local date. I put JSON code through Restlet to get a result but it gives me error 500 (wrong local date). How do I put local date to be correct?
I tried to write it like "date": "2012-03-19T07:22Z", "date": "2019-09-19", "date": 2019-09-19 and don't put it at all but it didn't work.
#Entity
public class Ad {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ad_id")
private int id;
private String name;
private LocalDate date;
private String text;
#Column(name = "price", precision = 10, scale = 2)
private BigDecimal price;
#ManyToOne
#JoinColumn(name = "author_fk_id")
private Author author;
#ManyToOne
#JoinColumn(name = "category_fk_id")
private Category category;
.....
}
#RestController
#RequestMapping("ad")
public class AdController {
#Autowired
#Qualifier("adServiceImpl")
private AdService<Ad> adService;
#PostMapping("/save")
public void save(#RequestBody Ad ad) {
adService.save(ad);
}
}
JSON
{
"id": 0,
"name": "house for sale",
"text": "selling a house in barry ave, 5 ears old",
"date": 2019-09-20,
"price": 250.00,
"author": {
"id": 0,
"name": "Liza",
"phone": {
"id": 0,
"number": "3121001111"
},
"address": {
"id": 0,
"country": "RUSSIA",
"city": "MOSCOW"
},
"email": {
"id": 0,
"email": "liza#mail.ru"
}
},
"category": {
"id": 0,
"name": "houses"
}
}
Instead of using private LocalDate date; use the java.sql.date type and pass the date in json like this "date": 2019-09-20,
it will work and if you want to convert that date formate to other local date formate use the SimpleDateFormat and parse it.

how to return customized json as output in Springboot JPA?

I have two entities responsible for fetching data from two different tables and i have created a class to display the combined result of those two entities. I am able to fetch the data using JPA but the output is not in the desired JSON format.
First Entity,
#Entity
#Table(name="basicinfo")
public class Rfx_BasicInfo implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true)
private String uniqueid;
private String description;
private String uniqueidstatus;
private LocalDateTime startdate;
private LocalDateTime enddate;
private String createdby;
public String getCreatedby() {
return createdby;
}
public Long getId() {
return id;
}
public String getUniqueid() {
return uniqueid;
}
public String getDescription() {
return description;
}
public LocalDateTime getStartdate() {
return startdate;
}
public LocalDateTime getEnddate() {
return enddate;
}
public String getUniqueidstatus() {
return uniqueidstatus;
}
}
and the Second one,
#Entity
#Table(name="supplierinvite")
public class Rfx_SupplierInvite {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String externalstatus;
private String uniqueid;
public String getExternalstatus() {
return externalstatus;
}
public String getUniqueid() {
return uniqueid;
}
}
These two are the repositories for the entities i created.
#Repository("rfxBasicInfoRepository")
public interface Rfx_BasicInfoRepository extends JpaRepository<Rfx_BasicInfo,Long> {
#Query(value="SELECT u from Rfx_BasicInfo u where u.createdby = :createdby")
List<Rfx_BasicInfo> findReportByLoginId(#Param("createdby") String createdby);
}
#Repository("rfxSupplierInviteRepository")
public interface Rfx_SupplierInviteRepository extends JpaRepository<Rfx_SupplierInvite,Long> {
#Query(value="SELECT u.uniqueid, u.externalstatus, count(u.externalstatus) from Rfx_SupplierInvite u where u.uniqueid in (:uniqueid) group by u.uniqueid,u.externalstatus order by u.uniqueid, u.externalstatus")
List<Rfx_SupplierInvite> findReportByUniqueId(#Param("uniqueid") String uniqueid);
}
In the first repository i'm fetching the data based on the "createdby" field and in the second one i'm fetching the data based on "uniqueid" field. This "uniqueid" field is common between the two tables. As of now, i have tried to fetch the results separately and merge them into one using a class. I didn't use any JPA mapping and Joins because i'm not sure how to use it here properly.
public class Rfx_Model {
private List<Rfx_BasicInfo> rfx_basicInfoList;
private List<Rfx_SupplierInvite> rfx_supplierInviteList;
public Rfx_Model(List<Rfx_BasicInfo> rfx_basicInfoList, List<Rfx_SupplierInvite> rfx_supplierInviteList) {
this.rfx_basicInfoList = rfx_basicInfoList;
this.rfx_supplierInviteList = rfx_supplierInviteList;
}
public List<Rfx_BasicInfo> getRfx_basicInfoList() {
return rfx_basicInfoList;
}
public List<Rfx_SupplierInvite> getRfx_supplierInviteList() {
return rfx_supplierInviteList;
}
}
This is my controller,
#RequestMapping(value="/buyerLandingReport/{LoginID}",method = RequestMethod.GET)
public ResponseEntity<Object> buyerLandingReport(#PathVariable("LoginID") String LoginID) {
try{
List<Rfx_BasicInfo> list1 = rfxBasicInfoRepository.findReportByLoginId(LoginID);
List<Rfx_SupplierInvite> list2 = rfxSupplierInviteRepository.findReportByUniqueId(list1.get(0).getUniqueid());
return new ResponseEntity(new Rfx_Model(list1,list2),HttpStatus.OK);
}
catch (Exception ex){
throw ex;
}
}
Below is the current JSON ouput,
{
"Rfx_BasicInfo": [
{
"id": 1,
"uniqueid": "RA001",
"description": "sbhdjajd",
"uniqueidstatus": "ajsd",
"startdate": "2018-05-04T12:00:00",
"enddate": "2018-05-04T12:00:00",
"createdby": "RIL01"
},
{
"id": 2,
"uniqueid": "RA001",
"description": "kasksj",
"uniqueidstatus": "sjkds",
"startdate": "2018-05-04T12:00:00",
"enddate": "2018-05-04T12:00:00",
"createdby": "RIL01"
},
{
"id": 3,
"uniqueid": "RA002",
"description": "asjhkdj",
"uniqueidstatus": "asjhd",
"startdate": "2018-05-04T12:00:00",
"enddate": "2018-05-04T12:00:00",
"createdby": "RIL01"
}
],
"Rfx_SupplierInvite": [
[
"uniqueid": "RA001",
"externalstatus":"AC",
"count": 1
],
[
"uniqueid": "RA001",
"externalstatus": "IN",
"count": 2
]
]
}
and the desired JSON ouput format is this one,
[
{
"Rfx_BasicInfo": {
"id": 1,
"uniqueid": "RA001",
"description": "Auction for taking bid for work on route Tirora Gondiya",
"uniqueidstatus": "PB",
"startdate": "2018-05-04T12:00:00",
"enddate": "2018-05-04T14:00:00",
"createdby": "RIL03"
},
"Rfx_Supplier" : [
{
"uniqueid": "RA001",
"externalstatus": "AC",
"count": 1
},
{
"uniqueid": "RA001",
"externalstatus": "IN",
"count": 2
}
]
},
{
"Rfx_BasicInfo": {
"id": 2,
"uniqueid": "RA002",
"description": "Auction for taking bid for work on route Gondiya  -  Amgaon",
"uniqueidstatus": "DR",
"startdate": "2018-05-04T14:00:00",
"enddate": "2018-05-04T16:00:00",
"createdby": "RIL03"
},
"Rfx_Supplier" : [
{
"uniqueid": "RA002",
"ExternalStatus": "AC",
"count": 1
},
{
"uniqueid": "RA002",
"ExternalStatus": "IN",
"count": 2
}
]
}
]
I'd really appreciate any suggestions to help me figure out the solution for this.
You really need the ResponseEntity ? If not, the common way is:
#RestController
public class MyController {
#RequestMapping(value="/buyerLandingReport/{LoginID}")
public Rfx_Model buyerLandingReport(#PathVariable("LoginID") String LoginID) {
try{
List<Rfx_BasicInfo> list1 = rfxBasicInfoRepository.findReportByLoginId(LoginID);
List<Rfx_SupplierInvite> list2 = rfxSupplierInviteRepository.findReportByUniqueId(list1.get(0).getUniqueid());
return new Rfx_Model(list1,list2);
}
catch (Exception ex){
throw ex;
}
}
Change your Controller as
#RequestMapping(value="/buyerLandingReport/{LoginID}",method = RequestMethod.GET)
public ResponseEntity<Object> buyerLandingReport(#PathVariable("LoginID") String LoginID) {
try{
List<Rfx_BasicInfo> list1 = rfxBasicInfoRepository.findReportByLoginId(LoginID);
List<Rfx_SupplierInvite> list2 = rfxSupplierInviteRepository.findReportByUniqueId(list1.get(0).getUniqueid());
List<Rfx_Model> body = Arrays.asList(new Rfx_Model(list1, list2);
return new ResponseEntity(body), HttpStatus.OK);
}
catch (Exception ex){
throw ex;
}
}
You need to return a list (or array) to get a json array response.

How can I return multi-level json using Hibernate/JPA in Spring Boot

I have a Postgres Database that has 4 tables Parents, Children, Groups and Group_Membership.
Groups can have multiple parents and Parents can have multiple groups. Parents can have multiple children but children can only have one parent.
This is the dumbed down version of the schema.
I am using Spring Boot with Hibernate JPA.
Parent.java
#Entity
#Table(name = "parents")
public class Parent {
#Id
#GeneratedValue
#Column(name="parent_id")
private Long parentId;
#Column(name= "first_name")
private String firstName;
#Column(name= "last_name")
private String lastName;
#OneToMany(mappedBy="parent")
private Set<Child> children;
#ManyToMany(cascade = { CascadeType.ALL })
#JoinTable(
name= "Group_Membership",
joinColumns = { #JoinColumn(name = "parent_id") },
inverseJoinColumns = { #JoinColumn(name = "group_id") }
)
private Set<Group> groups = new HashSet<>();
//Constructor
//Getters and Setters
}
Child.java
#Entity
#Table(name = "children")
public class Child {
#Id
#GeneratedValue
#Column(name= "child_id")
private Long childId;
#Column(name= "first_name")
private String firstName;
#Column(name= "last_name")
private String lastName;
#ManyToOne
#JoinColumn(name="parent_id", nullable=false)
private Parent parent;
//Constructor
//Getters and Setters
}
Group.java
#Entity
#Table(name = "groups")
public class Group {
#Id
#GeneratedValue
#Column(name= "group_id")
private Long groupId;
private String name;
#ManyToMany(mappedBy = "groups")
private Set<Parent> parents = new HashSet<>();
//Constructor
//Getters and Setters
}
I have repositories for all of them set up like this:
public interface GroupRepository extends PagingAndSortingRepository<Group, Long> {
#RestResource(rel = "name-contains", path = "containsName")
Page<Group> findByNameContains(#Param("name") String name, Pageable page);
}
Group Membership Table
CREATE TABLE GROUP_MEMBERSHIP (
PARENT_ID INT NOT NULL,
GROUP_ID INT NOT NULL,
PRIMARY KEY (PARENT_ID, GROUP_ID),
CONSTRAINT GROUP_MEMBERSHIP_IBFK_1
FOREIGN KEY (PARENT_ID) REFERENCES PARENTS (PARENT_ID),
CONSTRAINT GROUP_MEMBERSHIP_IBFK_2
FOREIGN KEY (GROUP_ID) REFERENCES GROUPS (GROUP_ID)
);
When I go to http://localhost:8080/groups
I get this response:
{
"_embedded": {
"groups": [
{
"name": "Hyde Park",
"_links": {
"self": {
"href": "http://localhost:8080/groups/1"
},
"group": {
"href": "http://localhost:8080/groups/1"
},
"parents": {
"href": "http://localhost:8080/groups/1/parents"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/groups"
},
"profile": {
"href": "http://localhost:8080/profile/groups"
},
"search": {
"href": "http://localhost:8080/groups/search"
}
},
"page": {
"size": 20,
"totalElements": 1,
"totalPages": 1,
"number": 0
}
}
Then when I want to look at the parents in the group I go to http://localhost:8080/groups/1/parents
Response
{
"_embedded": {
"parents": [
{
"firstName": "Cherice",
"lastName": "Giannoni",
"_links": {
"self": {
"href": "http://localhost:8080/parents/1"
},
"parent": {
"href": "http://localhost:8080/parents/1"
},
"groups": {
"href": "http://localhost:8080/parents/1/groups"
},
"children": {
"href": "http://localhost:8080/parents/1/children"
}
}
},
{
"firstName": "Aylmer",
"lastName": "Feckey"
"_links": {
"self": {
"href": "http://localhost:8080/parents/2"
},
"parent": {
"href": "http://localhost:8080/parents/2"
},
"groups": {
"href": "http://localhost:8080/parents/2/groups"
},
"children": {
"href": "http://localhost:8080/parents/2/children"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/groups/1/parents"
}
}
}
Finally when I want to see the children of the first parent in the group I go to http://localhost:8080/parents/1/children
Response
{
"_embedded": {
"children": [
{
"firstName": "Richard",
"lastName": "Giannoni"
"_links": {
"self": {
"href": "http://localhost:8080/children/2"
},
"child": {
"href": "http://localhost:8080/children/2"
},
"parent": {
"href": "http://localhost:8080/children/2/parent"
}
}
},
{
"firstName": "Deeanne",
"lastName": "Giannoni"
"_links": {
"self": {
"href": "http://localhost:8080/children/1"
},
"child": {
"href": "http://localhost:8080/children/1"
},
"parent": {
"href": "http://localhost:8080/children/1/parent"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/parents/1/children"
}
}
}
I would like to be able to call one endpoint like http://localhost:8080/groups/search/findAllGroupMembers?group_id=1
and have it return multi level json with the group, all parents in the group, and all of the children of each parent.
I know how to write a query with sub-queries to return this information, but I was just curious if there is a more "JPA/Hibernate" way to do this?
Thanks!
EDIT: Fixed using Alan Hay's answer
GroupFullProjection.java
#Projection(name = "groupFullProjection", types = {Group.class})
public interface GroupFullProjection {
Long getGroupId();
String getName();
Set<ParentFullProjection> getParents();
}
ParentFullProjection.java
#Projection(name = "parentFullProjection", types = {Parent.class})
public interface ParentFullProjection {
Long getParentId();
String getFirstName();
String getLastName();
Set<Child> getChildren();
}
json Response with all required information
Endpoint: http://localhost:8080/groups/1?projection=groupFullProjection
{
"name": "Hyde Park",
"groupId": 1,
"parents": [
{
"children": [
{
"firstName": "Richard",
"lastName": "Giannoni",
},
{
"firstName": "Deeanne",
"lastName": "Giannoni",
}
],
"parentId": 1,
"firstName": "Cherice",
"lastName": "Giannoni",
"_links": {
"self": {
"href": "http://localhost:8080/parents/1{?projection}",
"templated": true
},
"groups": {
"href": "http://localhost:8080/parents/1/groups"
},
"children": {
"href": "http://localhost:8080/parents/1/children"
}
}
},
{
"children": [
{
"firstName": "Hanson",
"lastName": "Feckey",
}
],
"parentId": 2,
"firstName": "Aylmer",
"lastName": "Feckey",
"_links": {
"self": {
"href": "http://localhost:8080/parents/2{?projection}",
"templated": true
},
"groups": {
"href": "http://localhost:8080/parents/2/groups"
},
"children": {
"href": "http://localhost:8080/parents/2/children"
}
}
}
],
"_links": {
"self": {
"href": "http://localhost:8080/groups/1"
},
"group": {
"href": "http://localhost:8080/groups/1{?projection}",
"templated": true
},
"parents": {
"href": "http://localhost:8080/groups/1/parents"
}
}
}
Lazy/Eager loading has absolutely nothing to do with it. The REST services in your application are provided by Spring Data Rest and having an understanding of why it looks like it does and how you can change it is probably worth learning.
https://docs.spring.io/spring-data/rest/docs/current/reference/html/#repository-resources.fundamentals
Essentially you get the links to the associations because these entities have their own repositories which are exposed as REST respources. If children/parent were not exposed as REST reources then the data would be inlined (as there would be no other way to access them).
You can however use Projections to fetch alternative views of the data. So you can then define a projection that would inline the assocations. Clients can request this specific view of the data.
e.g. http://localhost:8080/groups/1?projection=groupFullProjection
This involves creating a simple interface that defines the properties to be exposed in that view of the data.
See: https://docs.spring.io/spring-data/rest/docs/current/reference/html/#projections-excerpts
This might look something like:
#Projection(name = "parentFullProjecton", types = { Parent.class })
interface ParentFullProjection{
// inline the child collection
Set<Child> getChildren();
// other fields
}
#Projection(name = "groupFullProjection", types = { Group.class })
interface GroupFullProjection{
//inline the parents collection and use the view which inlines Children
Set<ParentFullProjecton> getParent();
// other fields
}

Jackson JSON not expanding attributes of object

I'm facing a weird problem with JSON serialization. I've JPA objects which I'm using annotation #JsonProperty to serialize & deserialize objects to JSON data.
Query. java
#Entity
#XmlRootElement
public class Query {
#Id
#GeneratedValue
private int queryId;
#ManyToOne
#JoinColumn(name="institution_id")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property="institutionId")
private InstitutionDetails institution;
#ManyToOne
#JoinColumn(name="department_id")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property="deptId")
private Department department;
#ManyToOne
#JoinColumn(name="topic_id")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property="topicId")
private Topic topic;
#ManyToOne
#JoinColumn(name="raised_by_user_id")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property="email")
private User raisedByUser;
#Lob
private String query;
#Column(name="query_date")
private Date queryDate;
#Column(name="query_answered")
private boolean queryAnswered;
#OneToMany(cascade=CascadeType.ALL, mappedBy="query", fetch=FetchType.LAZY)
private Set<Response> responses;
#JsonProperty
public int getQueryId() {
return queryId;
}
public void setQueryId(int queryId) {
this.queryId = queryId;
}
#JsonProperty
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
#JsonProperty
public Topic getTopic() {
return topic;
}
public void setTopic(Topic topic) {
this.topic = topic;
}
#JsonProperty
public User getRaisedByUser() {
return raisedByUser;
}
public void setRaisedByUser(User raisedByUser) {
this.raisedByUser = raisedByUser;
}
#JsonProperty
public String getQuery() {
return query;
}
public void setQuery(String query) {
this.query = query;
}
#JsonProperty
public Date getQueryDate() {
return queryDate;
}
public void setQueryDate(Date queryDate) {
this.queryDate = queryDate;
}
#JsonProperty
public boolean isQueryAnswered() {
return queryAnswered;
}
public void setQueryAnswered(boolean queryAnswered) {
this.queryAnswered = queryAnswered;
}
#JsonProperty
public Set<Response> getResponses() {
return responses;
}
public void setResponses(Set<Response> responses) {
this.responses = responses;
}
#JsonProperty
public InstitutionDetails getInstitution() {
return institution;
}
public void setInstitution(InstitutionDetails institution) {
this.institution = institution;
}
}
Department.java
#Entity
#XmlRootElement
public class Department {
#Id
#GeneratedValue
private int deptId;
#Column(name="department_name", length=100)
private String departmentName;
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY)
#JoinColumn(name="department_id")
private Set<Topic> topic;
#OneToMany(cascade=CascadeType.ALL, mappedBy="department", fetch=FetchType.LAZY)
private Set<Query> queries;
#JsonProperty
public int getDeptId() {
return deptId;
}
public void setDeptId(int deptId) {
this.deptId = deptId;
}
#JsonProperty
public String getDepartmentName() {
return departmentName;
}
public void setDepartmentName(String departmentName) {
this.departmentName = departmentName;
}
#JsonProperty
public Set<Topic> getTopic() {
return topic;
}
public void setTopic(Set<Topic> topic) {
this.topic = topic;
}
#JsonIgnore
public Set<Query> getQueries() {
return queries;
}
public void setQueries(Set<Query> queries) {
this.queries = queries;
}
}
QueryController.java
#GET
#Path("/getAllAnsweredQueries/{institutionId}/{departmentId}/{topicId}")
#Produces({MediaType.APPLICATION_JSON})
public List<Query> getAllAnsweredQueries(#PathParam("institutionId") int institutionId, #PathParam("departmentId") int departmentId, #PathParam("topicId") int topicId) {
return m_queryService.getAllAnsweredQueries(institutionId, departmentId, topicId);
}
The above method returns a List
But the Serialized JSON object doesn't contain the entire child object details from the 2nd item in the list. The 1st JSON object has everything correctly. Then 2nd JSON object in the list is missing some object details:
Output JSON
[
{
"queryId": 7,
"institution": {
"institutionId": 1004,
"instituionName": "A College"
},
"department": {
"deptId": 1,
"departmentName": "Anatomy",
"topic": [
{
"topicId": 1003,
"topicName": "Nervous System"
},
{
"topicId": 1002,
"topicName": "Muscular System"
},
{
"topicId": 1006,
"topicName": "Vascular System"
},
{
"topicId": 1005,
"topicName": "Cells & Tissues"
},
{
"topicId": 1004,
"topicName": "Circulatory Sytem"
},
{
"topicId": 1001,
"topicName": "Skeletal System"
}
]
},
"topic": {
"topicId": 1001,
"topicName": "Skeletal System"
},
"raisedByUser": {
"email": "abcd#gmail.com",
"userName": "User A",
"userType": {
"userTypeId": 10001,
"userType": "Student"
},
"institutionDetails": 1004,
"registeredDate": 1439136336000,
"mobileNumber": "12346578"
},
"query": "What causes bone damage ?",
"queryDate": 1439139172000,
"queryAnswered": false,
"responses": []
},
{
"queryId": 6,
"institution": 1004,
"department": 1,
"topic": {
"topicId": 1002,
"topicName": "Muscular System"
},
"raisedByUser": "abcd#gmail.com",
"query": "What is the cause of spine disc lapse ?",
"queryDate": 1439137989000,
"queryAnswered": true,
"responses": [
{
"responseId": 2,
"query": {
"queryId": 6,
"institution": 1004,
"department": 1,
"topic": 1002,
"raisedByUser": "abcd#gmail.com",
"query": "What is the cause of spine disc lapse ?",
"queryDate": 1439137989000,
"queryAnswered": true,
"responses": [
{
"responseId": 2,
"query": 6,
"respondedByUser": {
"email": "mnop#gmail.com",
"userName": "User B",
"userType": {
"userTypeId": 10002,
"userType": "Expert"
},
"institutionDetails": 1004,
"registeredDate": 1439136400000,
"mobileNumber": "12346578"
},
"response": "Incorrect seating position",
"responseDate": 1439138916000
}
]
},
"respondedByUser": "mnop#gmail.com",
"response": "Incorrect seating position",
"responseDate": 1439138916000
}
]
}
]
If you notice in the 1st result (queryId: 7), department Object & respondedByUser object will be expanded whereas in the 2nd result (queryId: 6) the objects department & respondedByUser are not expanded. When I debug & see the values before the serialization, the objects are having their respective values properly.
Why is it happening so ? Am I missing something ?
That is because you are asking Jackson to use Object Ids, with annotation #JsonIdentityInfo. So after initially serializing objects completely, further references refer to the object id.

Categories