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.
Related
I'v been searching internet for answer, but nothing was working for me. There is a lot of topics with similar cases but specific details are different in a way that make them unusable for me.
So I have two tables: t_item and t_item_info:
item_id field from t_item_info table references id field from t_item table. I'm using mysql db and id column form t_item is auto incremented
I need to make unidirectional one-to-one mapping in a specific way. Here are my classes:
#Entity
#Table(name = "t_item")
public class Item {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false)
#PrimaryKeyJoinColumn(name = "id", referencedColumnName = "item_id")
private ItemInfo info;
}
And other one
#Entity
#Table(name = "t_item_info")
public class ItemInfo {
#Id
private Long itemId;
private String descr;
}
So the point is that i need Item object to have a reference to ItemInfo object. NOT The other way!
Item -> ItemInfo --YES
Item <- ItemInfo --NO
The other thing is that i need parent (Item) id to become id for a child (ItemInfo)
For example I create Item object with null id and set it's info field with ItemInfo object which also have null id field. Like this:
{
"id": null,
"name": "Some name",
"info": {
"itemId": null,
"descr": "some descr"
}
}
Then when Item object persists hibernate should generate id for parent(Item) object and set it as itemId field for child(ItemInfo).
I have been trying to achieve this with different hibernate annotations and I noticed that no matter how hard I tried Hibernate always seems to try to persist child object first. I noticed it in the logs when I turned sql logging on. insert into t_item_info always goes first (and dies because of null id :D)
So the question is: Is it even possible to achieve this and if so what should I change in my code to do so
I hope that what I'm trying to ask makes sens to you given my poor explanations =)
Why people always insist the child object table in one-to-one associations should be the one with the foreign key is beyond me.
Anyway, as a workaround, since both objects share the id and the association is non-optional, you might as well declare the autogenerated key for the child object:
#Entity
#Table(name = "t_item_info")
public class ItemInfo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long itemId;
private String descr;
}
and then use #MapsId for the parent:
#Entity
#Table(name = "t_item")
public class Item {
#Id
private Long id;
private String name;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "id")
#MapsId
private ItemInfo info;
}
Note that this approach, will, in a sense, fool Hibernate into thinking it is the Item that should be treated as the child object. You have been warned.
While there is an accepted answer here it looks to me like #Secondary table would be a better and more convenient solution: you may have 2 tables at the database level but I do not see any reason that that fact needs be exposed to any client code. There does not seem to be a lot of benefit to that? The following gives you a simpler API.
Entity:
#Entity
#Table(name = "t_item")
#SecondaryTable("t_item_info", pkJoinColumns={
#PrimaryKeyJoinColumn(name="id", referencedColumnName="item_id")})
public class Item {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#Colulumn(name = "description", table= "t_item_info"")
private String description;
}
API:
{
"id": null,
"name": "Some name",
"descr": "some descr"
}
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
maybe another many-to-many relationship issue with spring data-jpa and how to update an existing entity with another existing entities.
I'll put a short version of my Entities just for clarify only when the error occurs.
I have a Peticion entity:
#Entity
#Table(name = "peticiones")
#JsonIdentityInfo(generator =
ObjectIdGenerators.PropertyGenerator.class,property = "id", scope =
Peticion.class)
#Validated
public class Peticion
{
private int id;
private Usuario usuario;
private Categoria categoria;
private Set<Tag> tags;
#ManyToMany( fetch = FetchType.LAZY, cascade = {
CascadeType.MERGE
} )
#JoinTable( name="peticion_tag", joinColumns= #JoinColumn(name = "peticion_id", referencedColumnName="id"), inverseJoinColumns = #JoinColumn(name="tag_id", referencedColumnName="id") )
public Set<Tag> getTags() {
return tags;
}
public void setTags(Set<Tag> tags) {
this.tags = tags;
}
And Tag entity:
#Entity
#Table(name = "tags")
#JsonIdentityInfo(generator =
ObjectIdGenerators.PropertyGenerator.class,property = "id", scope =
Tag.class)
public class Tag
{
private int id;
#Size( min = 4 )
private String nombre;
private Set<Peticion> peticiones;
private Set<Categoria> categorias;
private Set<Usuario> usuarios;
#ManyToMany( mappedBy="tags" )
public Set<Peticion> getPeticiones() {
return peticiones;
}
public void setPeticiones(Set<Peticion> peticiones) {
this.peticiones = peticiones;
}
Ok, so when I try to put or patch one Peticion in the format of:
{
"id": 123,
"usuario":{
"id": 5
},
"categoria":{
"id": 7
},
"tags":[
{
"id":3
},
{
"id":10
}
]
}
When I send this information, I got an error that says that I have a constraint violation saying that name I suppose the one property for Tag is null... So I figure it out that this is trying to create another entity, but that's not the case I wanna do, I wanna update the relationships between Peticion and Tag, and for example if I do this:
{
"id": 123,
"usuario":{
"id": 5
},
"categoria":{
"id": 7
},
"tags":[]
}
It works perfectly, I mean it deletes the relationship tags that were before. So I don't know if I'm sending the json correctly or do I have to put another configuration annotation or something in my Entities.
Note: I'm using JpaRepository for saving/updating and my controller only calls the method save.
Thank you
You might be missing fetches on the relation entity on your database layer, I think you should also share the repositories and queries that you use to fetch the linked data.
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.
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.