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.
Related
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
I have an Account object containing a OneToMany relation with Beneficiary object and this relationship is bi-directional so I have a ManyToOne relation in the Beneficiary Object with Account Object
public class Account {
#Id
#GeneratedValue
private Long id;
private String name;
private String number;
//Other fields
#OneToMany(mappedBy = "account", cascade = CascadeType.ALL, orphanRemoval = true)
#JsonManagedReference
private List<Beneficiary> beneficiaries = new ArrayList<>();
}
public class Beneficiary {
#Id
#GeneratedValue
private Long id;
//Other fields
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "account_id")
#OnDelete(action = OnDeleteAction.CASCADE)
private Account account;
}
In the JSON response, I need the Account information containing the list of Beneficiaries and for each Beneficiary I just need the Account name and Account number. Is it possible to serialize it somehow so that I get response in this fashion? Or do I need to modify my entity structures?
Sample Account Response -
{
"id": 123,
"name": "Name1",
"number": "111111",
"beneficiaries": [
{
"id": 1,
"account": {
"name": "Name2",
"number": "222222"
}
},
{
"id": 2,
"account": {
"name": "Name3",
"number": "333333"
}
}
]
}
You are not supposed to serialize your JPA objects. Instead, you need to define domain objects. These are objects are the ones to be serialize and exposed to the business. This abstraction decouples your REST or SOAP or whatever interface with your JPA layer.
I would create a domain class for your account class. Call it AccountDTO or something like that. Any object being returned from your JPA repositories need to be mapped to this DTO objects and bubbled up to the services layer. Then your DTO is the class which models your business needs. In there you can just put the accounts and the beneficiaries names.
DTO stands for Data Transfer Objects. These are the ones supposed to be serialized and sent between systems.
One idea would be to use a custom serializer.
You would have to write a custom serializer, similar to this:
public class NestedAccountSerializer extends StdSerializer<Account> {
public NestedAccountSerializer() {
this(null);
}
public NestedAccountSerializer(Class<Account> t) {
super(t);
}
#Override
public void serialize(Account account, JsonGenerator generator, SerializerProvider provider) throws IOException {
generator.writeObject(new AccountView(account.getName(), account.getNumber()));
}
private static class AccountView {
#JsonProperty
private final String name;
#JsonProperty
private final String number;
AccountView(String name, String number) {
this.name = name;
this.number = number;
}
}
}
And then use it like this in your Beneficiary class:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "account_id")
#OnDelete(action = OnDeleteAction.CASCADE)
#JsonSerialize(using = NestedAccountSerializer.class)
private Account account;
Please, let me know if it helped.
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.
For implementing a REST API in Java, I'm using:
- Jersey JAX-RS Framework
- Genson parser
- Tomcat8 server
- Hibernate
I have this method in a service class:
#POST
#Consumes("application/json")
public Response createUser(Users user) {
UsersDAO u = new UsersDAO();
if(user.getLogin() == null || user.getPasswd() == null)
return Response.status(Response.Status.BAD_REQUEST).entity("Missing information").build();
try{
u.addUser(user);
}catch(HibernateException e){
return Response.status(Response.Status.BAD_REQUEST).entity("User already exists").build();
}
return Response.status(Response.Status.CREATED).build();
}
The Users class:
public class Users implements Serializable {
#Basic(optional = false)
#Column(name = "passwd")
private String passwd;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "iduser")
private Integer idUser;
#Basic(optional = false)
#Column(name = "login")
private String login;
#JoinColumn(name = "idusersgroup", referencedColumnName = "idusersgroup")
#ManyToOne(optional = true)
private UsersGroups idUsersGroup;
#Transient
private int idGroup;
.
.
.
}
As you can see, I created the idGroup field just to store the id of the UsersGroups object related, so if I want to add the group in the moment of the user creation, I'll just have to include its id in the JSON instead of the UsersGroups object (the relationship is optional, a user can belong to a group or not). The problem is Genson is not adding this field to the Users object when consumes the JSON:
{
"login": "admin1",
"passwd": "12345",
"idgroup": 3
}
If I POST this JSON and then access to the user.getIdGroup(), it returns 0, so I assume that idGroup field isn't being consumed. Could the #Transient annotation has something to do with this issue? Any idea on how to solve it? If the problem is Genson and there's any solution using another JSON parser (like Jackson), I could consider a switch
The issue is that in the json you have idgroup with lowercase while the property in the class is with upper case G. Also make sure you have getters and setters for it or configure Genson to use private properties (you can find a few examples in the docs).
BTW genson is not aware of hibernate annotations.
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.