JPA all referenced relations are retrieved (ManyToMany - ManyToOne - OneToMany) - java

Working on this 'twitter' application where a user can have posts #OneToMany and can have followers #ManyToMany.
While retrieving a user all it's posts and followers get retrieved as well.
This is all correct but it's also retrieving every 'poster' for each post (which is the user itself) and for each follower, all it's posts and followers.
I can't figure out how to limit this to the user itself.
User
#Entity
#Table(name = "User")
#NamedQueries({
#NamedQuery(name = "User.findAll", query = "SELECT u FROM User u"),
#NamedQuery(
name = "User.auth",
query = "SELECT u FROM User u WHERE u.username = :username AND u.password = :password"
)
})
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true, nullable = false)
private String username;
#Column(nullable = false)
private String password;
#ManyToMany
#OneToMany(mappedBy = "poster", cascade = CascadeType.ALL)
private List<Post> posts = new ArrayList<>();
#JoinTable(name = "Followers",
joinColumns = {
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
},
inverseJoinColumns = {
#JoinColumn(name = "FOLLOWER_ID", referencedColumnName = "ID")
}
)
private List<User> followers = new ArrayList<>();
.... constructor, getters and setters
Post
#Entity
#Table(name = "Post")
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String content;
#ManyToOne
private User poster;
.... constructor, getters and setters
Outcome I get vs what I want
{
"id": 1,
"username": "jim",
"posts": [
{
"id": 1,
"content": "Post 1 by jim",
"poster": {
// ^ this is the user itself (I don't need this one)
"id": 1,
"username": "jim",
"posts": [
// keeps recurse
]
}
}
],
"followers": [
{
"id": 2,
"username": "follower1",
"posts": [
{
"id": 4,
"content": "Post 2 by follower 1",
"poster": {
// ^ this is the follower itself (I don't need this one)
"id": 2,
"username": "follower1",
"posts": [
// Same issue
]
}
}
],
"followers": [], // <-- I don't need this one either
}
]
}
Well it's pretty clear that fetching one user fill keeps fetching all it's relations which are recursive.
Is this a designer's fault or can this be ignored/limited?
Note: I am using Gson to serialise objects to JSON format
Update
Tried to use:
#ManyToOne(fetch = FetchType.LAZY)
private User poster;
Which works but still gets the following extra prop in JSONso not sure if this is a neath solution:
"_persistence_poster_vh": {
"sourceAttributeName": "poster",
"isInstantiated": false,
"row": {
"Post.ID": 3,
"Post.CONTENT": "Post 3 by jim",
"Post.DATETIME": "2018-01-22",
"Post.POSTER_ID": 1
},
"isCoordinatedWithProperty": false
}
And
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
...
)
private List<User> followers = new ArrayList<>();
Which still returns all followers (which I want!) I just don't want the followers.followers and followers.posts..

Best guess: it’s not actually fetching these objects until you try to dereference them.
Be default, JPA will eager fetch #OneToOne and #OneToMany relations, but not #ManyToOne or #ManyToMany. What happens is that when you reference these fields, it will then go and fetch the actual contents of the list.
How can you tell this is happening? Check the list’s class using getFollowers().getClass()
What you see won’t be a LinkedList or an ArrayList but a class from your JPA provider, probably with “Lazy” somewhere in the name. When you call Size or Get on the list, it will perform the fetch.
You can set OneToOne and OneToMany relations to be lazy as well, and use EntityGraphs to determine what entities you want to eagerly fetch as well. JPA has a number of gotchas like this.
I’ve seen GSON mentioned, and just a warning: it won’t be aware of the lazy loading lists, so you MUST tell It to avoid the properties you don’t want it to follow.
Typically with JSON marshaling, you’ll want it to ignore the parent object, so in Post, User should be ignored for example. Additionally links to same types should typically be ignored (followers) or else mapped specially, such that it doesn’t Marshall the entire object, but only produces an array of usernames. You can tell it to ignore the actual followers field, and have it marshal a getter which returns an array of usernames to implement this.

You can specify fetch=FetchType.LAZY in the annotation you don't want to fetch immediately. The downside is, that if you need the data later you have to access it in the scope of the still open session.

There are two ways to handle this -
You can either use #JsonIgnoreProperties(ignoreUnknown=true) anotation on attributes you want to skip while serializing the object.
Or you change your FetchType to FetchType.LAZY so that you can get the required data on need basis while preparing your JSON , rather than getting all records at once.

Related

How to select just a few data to display using JoinColumnn

In spring java especially in JoinColumn, if we do JoinColumn for example JoinColumn(name="guest_id"). Then all guest data will be displayed. Whereas I only want to retrieve only one data, which is the name only.
#Column(name = "guest_id")
private Integer guestId;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "guest_id", insertable = false, updatable = false)
private String guestName;
When a request is made at postman, the result will be like this
"guestCard": {
"id": 53,
"idPic": null,
"guestType": null,
"title": "mr",
"name": "RIKO JANUAR",
"phoneNumber": "08100000",
"email": "riko#email.com",
"gender": "male",
"bDay": "2019-10-29",
"nationality": "AF",
"idCard": "21321131231",
"validity": "2019-10-29",
"telpFax": null,
"address": "bandung",
"job": "Musician"
}
What I want is to take and display only one, which is the name. Like this
"guestCard": {
"name": "RIKO JANUAR"
}
I tried it this way but it didn't work
#Column(name = "guest_id")
private Integer guestId;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "name.guest_id", insertable = false, updatable = false) //this is not work
private String guestName;
Ignore all fields you want to hide by #JsonIgnoreProperties jackson-annotations on the relationship class.
Other solution, create various model class and transfer the fields.
As i understand you need dto choose what to select from entities. So create a dto and cast to the query result using projections. Refer Spring data projections -
#Projection(name="DtoProjection", types={Guest.class})
public interface DtoOnly {
Integer getGuestId();
#Value("#{target.guestCard.guestName}")
String getGuestName();
}
Another way is to use Constructor in HQL
#Query("SELECT new NewDto(guest.guestID, guest.guestCard.guestName FROM Guest guest)")
List<NewDto> findAllGuest();

A findById operation on the child entity in the 'One to many mapping' scenario returns recursive child entities. Need solution

I have 2 entities with me. JobOfferEntity and JobApplicationEntity. JobOfferEntity has one to many mapping with the JobApplicationEntity. Below are the entities :-
public class JobOfferEntity {
#JsonManagedReference
#OneToMany(mappedBy = "relatedJobOffer",fetch = FetchType.LAZY)
private List<JobApplicationEntity> jobApplications = new ArrayList<>();
}
public class JobApplicationEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "app_id")
private long applicationId;
#JsonBackReference
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "JOB_OFFER",updatable = false)
private JobOfferEntity relatedJobOffer;
}
I am trying to fetch the job application on the basis of applicationId;
JobApplicationEntity jobApplicationEntity = this.jobApplnRepo.findById(appId).orElseThrow(() -> new DataNotFoundException(""));
But this operation returns the job application entity , however the problem is it returns the child entity JobOfferEntity and again the job apllication entity embedded inside the Job Offer entity recursively . Please find below json
{
"jobId": 0,
"applicationId": 1,
"candidateEmail": "Lalit.mishra#gmail.com",
"resumeTxt": "Sample resume",
"applicationStatus": "APPLIED",
"relatedJobOffer": {
"jobId": 1,
"jobTitle": "Amruta dev",
"jobDesc": "Sample C++ scientist",
"contactPerson": "Basho",
"createdDate": "2019-03-03",
"modifiedDate": "2019-03-03",
"jobOfferStatus": "ACTIVE",
"jobApplications": [
{
"applicationId": 1,
"candidateEmail": "Lalit.mishra#gmail.com",
"resumeTxt": "Sample resume",
"applicationStatus": "APPLIED"
}
]
}
}
As mentioned above I only need the related job offer but I dont want the related job applications in the related job offers again recursively.
Could you please recommend me any solution?
If you don't need to fetch ( on the clientside) from JobEntity the related JobApplicationEntitys, you can annotate that field with #JsonIgnore
public class JobOfferEntity {
#JsonIgnore
#OneToMany(mappedBy = "relatedJobOffer",fetch = FetchType.LAZY)
private List<JobApplicationEntity> jobApplications = new ArrayList<>();
}
source: https://fasterxml.github.io/jackson-annotations/javadoc/2.5/com/fasterxml/jackson/annotation/JsonIgnore.html

Spring Jackson deserialization only picks the first item of an array

I building an api rest with spring boot. I have a parent-child relationship in witch the child its an array of objects.
The problem is that deserialization only picks the first item of the array. Everything else seems to work fine. The parent and the child are pesisted in the database too.
I send something like this:
"user": {
"name": "foo",
"childs": [
{
"name": "bar",
....
},
{
"name": "foobar",
....
}
],
....
}
But got persisted this:
"user": {
"id": 1,
"name": "foo",
"childs": [
{
"id": 1,
"name": "bar",
....
}
],
....
}
Any clue on this?
Update
Parent Entity:
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id",
scope = User.class)
#Entity( name = "users" )
#Table( name = "users" )
public class User extends ModelEntity {
Model's fields...
...
#JsonView( value = {DTOViews.PrivateProfile.class, DTOViews.Owner.class} )
#JsonManagedReference( value = "User-ProfessionalExperience" )
#OneToMany( mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY )
private Set<ProfessionalExperience> professionalExperiences;
}
Child entity:
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id",
scope = ProfessionalExperience.class)
#Entity
#Table( name = "professional_experiences")
public class ProfessionalExperience extends ModelEntity {
Model's fields...
...
#JsonBackReference( value = "User-ProfessionalExperience" )
#ManyToOne
#JoinColumn(name = "user_id", nullable = false)
private User user;
}
Controller:
#RequestMapping(method = RequestMethod.POST)
public MappingJacksonValue create(#RequestBody #Valid User userToCreate, BindingResult result) {
...
}
Thank you all in advance.
So, I finally solved it. The issue comes from the relationship collection type and hasCode() / equals() methods.
All the entities in my model extend from "ModelEntity" class. This class provides id and record active fields for all extending models and a hasCode/equals method based on these fields. As the relationship between "User" and "ProfessionalExperience" is defined as a set, it can't store duplicated elements.
So, to tell jackson that the children are different elements, we need to override hasCode/equals in each model class with the fields defined in each one.

Dynamically fetch associated entities

I have 3 entities
Project.java
#Entity
public class Project {
#Id
#GeneratedValue
private Long id;
#Column(nullable = false)
private String name;
#OneToMany(mappedBy = "project", fetch = FetchType.LAZY)
private List<ProjectReview> projectReviews = new ArrayList<>();
// getters
}
User.java
#Entity
public class User {
#Id
#GeneratedValue
private Long id;
#Column(nullable = false)
private String userName;
#OneToMany(mappedBy = "reviewer")
private List<ProjectReview> projectReviews = new ArrayList<>();
// getters
}
ProjectReview.java
#Entity
#IdClass(ProjectRevieweId.class)
public class ProjectReview {
#Id
private long projectId;
#Id
private long userId;
#Column(nullable = false)
private String review;
#ManyToOne
#JoinColumn(name = "userId", insertable = false, updatable = false)
private User reviewer;
#ManyToOne
#JoinColumn(name = "projectId", insertable = false, updatable = false)
private Project project;
// getters
}
Pretty simple many-to-many relationship with join table. This setup is not working, because when serialized with jackson to json format, it has infinite depth EVEN if the default fetch type is LAZY on collections (i dont understand why!?).
I am using standard Spring Repository->Service->RestController flow with Spring Boot 1.4.1 on MySQL.
I used the #JsonBackReference on ProjectReview.reviewer and ProjectReview.project but thats not what I want, because sometimes i want to have access to associated entities, and sometimes not. Explanation folllows:
When I call rest service GET ../projects, i would like to see
[{
"id":1,
"name":"project1",
"projectReviews":[{
"review":"My super review!",
"reviewer":{ -- this has to be included
"id":1,
"userName":"user1",
"projectReviews":null -- this cant be fetched as it causes recursion
},
"project":{ -- instance or null or entirely missing this attribute - as it is the same as root
"id":1,
"name":"project1",
"projectReviews":null -- this cant be fetched as it causes recursion
}
},
{
-- second review...
}]
},{
-- second project...
},
...etc
]
But when I call GET ../users, i would like to see
[{
"id":1,
"userName":"user1",
"projectReviews":[{
"review":"My super review!",
"reviewer":{ -- instance or null or entirely missing this attribute - as it is the same as root
"id":1,
"userName":"user1",
"projectReviews":null -- this cant be fetched as it causes recursion
},
"project":{ -- this has to be included
"id":1,
"name":"project1",
"projectReviews":null -- this cant be fetched as it causes recursion
}
},
{
-- second review...
}]
},{
-- second user
}
...etc
]
I hope you get it. projectReviews on top level should be eagerly fetched, but on second level they should not - beacuse it creates infinite depth structure.
How could I setup the fetching or entities to provide this king of structure?
Bonus question - Why are projectReviews fetched in json if default is LAZY fetching?
You can use follwoing annotations to solve the infinite recursion problem : #JsonManagedReference and #JsonBackReference.
Use #JsonManagedReference in User.java and Project.java for variable "projectReviews" and #JsonBackReference for "reviewer" and "project"
If you are not able to get reviewer and project under projectReviews, please use the annotations othey way around, #JsonManagedReference in projectReviews and vice versa.

JAX-RS RESTful API working with JSON representations and linking resources

Sometimes it's confusing how I should link resources within a RESTful API, consider for example the entities:
Profile (Users can create business profiles with address, details, etc..)
Plan (Already persisted in app's DB, created by administrators)
The request to create a Profile looks like:
POST /profiles
{
"name": "Business name",
"address": "The address",
"phone": "0000000000"
}
Now it is required that a Profile belongs to a Pricing Plan. So is it a good idea to do POST request like this with JSON?
POST /profiles
{
"name": "Business name",
"address": "The address",
"phone": "0000000000"
"plan": {
"id": 1
}
}
and then load the plan by the provided id and associate it with the profile being created:
#POST
#Path("/profiles")
public Response createProfile(Profile profile) {
// load plan resource from DB
Plan plan = em.find(Plan.class, profile.getPlan().getId())
// associate
profile.setPlan(plan);
// persist profile
em.perist(profile);
}
The Profile entity:
#Entity
public class Profile implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "plan_id", nullable = false)
private Plan plan;
private String name
...
// getters and setters
}
The Plan entity:
#Entity
public class Plan implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Column(nullable = false)
private String name;
#NotNull
#Column(nullable = false, columnDefinition = "text")
private String description;
#NotNull
#Column(nullable = false, precision = 8, scale = 2)
private BigDecimal price;
#NotNull
#Column(nullable = false)
private Integer days;
#OneToMany(fetch = FetchType.LAZY, mappedBy="plan", cascade = CascadeType.ALL)
private List<Profile> profiles;
...
}
In other words i am asking what I should pass to the request body in order to link a reference entity.
I would like to believe that something like this is more reasonable:
POST /plans/1/profiles
but according to the REST and JSON semantics what would be the best option?
I can also think of other ways such as providing the Plan id as a query param:
POST /profiles?planId=1
I would say you need to do the following:
Create profile with
POST: /profile
Assign plan with
PUT: /profile/<profile_id>
{
"name": <optional>,
"plan_id": <optional>,
...
}
First thing is you separate create and update (POST/PUT). Another is you state profile ID for update in URL.
You can set parameters you need to update in PUT request body and update only parameters which are set. Think it's fine with REST concept.

Categories