Hide ManyToOne field in REST API in Spring Boot - java

I am using Spring Boot with simple REST API on server. I have 2 resources: users and articles. Here is Article class:
#Entity(name = "article")
public class Article {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(nullable = false)
private String text;
#ManyToOne(fetch = FetchType.LAZY)
private User user;
public User getUser() { // I need this method.
return user;
}
// All other setters and getters.
}
Now, if I fetch some article by its ID using REST API, response looks like:
{
"id": 5,
"text": "Content of article...",
"user": {
"id": 1,
"username": "user#email.com",
"password": "$2a$10$CbsH93d8s5NX6Gx/N5zcwemUJ7YXXjRIQAE2InW9zyHlcTh6zWrua"
}
}
How can I exclude user field from response? If I remove Article.getUser method, everything works fine and response looks like:
{
"id": 5,
"text": "Content of article..."
}
This is desired result. However, I need Article.getUser, because e. g. if someone want delete article, I need check, if author of the request is author of the article, so user cannot delete articles of other users.

You can use #JsonIgnore on like below:
#JsonIgnore
private User user;
The other way is Projection in which you have more control on what should be included in response.

Related

many-to-many mapping with extra column - how to set mapping and how to serialise?

I am very new to Hibernate and I am trying to solve an issue similar to this question, specifically the answer.
I have something very similar set up (in my case it's Author, Book, and the mapping table AuthorBook). I am using this in Spring Data JPA, so I have the following components:
Repositories: AuthorRepository, BookRepository, AuthorBookRepository
Services: AuthorService, BookService, AuthorBookRepository
Controllers: AuthorController, BookController, AuthorBookController
My entities are:
#Entity
public class Author {
#Id
#GeneratedValue
private Long id;
private String name;
#OneToMany(mappedBy = "author")
private Set<AuthorBook> authorBooks;
// getters and setters
}
#Entity
public class Book {
#Id
#GeneratedValue
private Long id;
private String name;
#OneToMany(mappedBy = "book")
private Set<AuthorBook> authorBooks;
// getters and setters
}
#Entity
public class AuthorBook {
#Id
#GeneratedValue
private Integer id;
#ManyToOne
#JoinColumn(name = "user_id")
private Author author;
#ManyToOne
#JoinColumn(name = "book_id")
private Book book;
#Column(name = "isMainAuthor")
private boolean isMainAuthor;
// getters and setter
}
My understanding is that I should make the following POST requests:
Create an author:
{
"name": "Test Author"
}
Create a book:
{
"name": "Test Book"
}
Create the mapping:
{
"author": {
"id": 1
},
"book": {
"id": 2
},
"isMainAuthor": true
}
First of all: is this the correct way to use this? If no, how should I use it instead? If yes, how is serialisation supposed to work? Because if I do it like this, and then fetch the list of books, the response will be infinitely long like so:
[
{
"id": 2,
"name": "Test Book",
"authorBooks": [
{
"id": 3,
"author": {
"id": 1,
"name": "Test Author",
"authorBooks": [
{
"id": 3,
"author": {
"id": 1,
"name": "Test Author",
"authorBooks": [
{
"id": 3,
"author": {
"id": 1,
"name": "Test Author",
"authorBooks": [
...
I know that I could use #JsonIgnore on the Author and Book getters in AuthorBook, and #JsonProperty on the setters so that deserialisation still works. However, as someone who is unexperienced with Hibernate, this seems like a hack to me. What is the cleanest and best way to solve this?
Also, is there a way to update the mapping via the author and book endpoints directly? I might already have an author in the database and want to just add a new book he wrote by adding a new Book entity and providing the relation as part of it, but I seem not to be able to do that.
This is absolutely not a hacker trick. With the #JsonIgnore annotation, this problem is solved. This problem is called infinite recursion with the release of Jackson JSON and nothing can be done with it. For this, they came up with an annotation.
You probably need to set an embedded ID within the mapping table. Keeping the Author and Book table as such, create a new class
#Embeddable
public class AuthorBookId{
#Column(name = "user_id")
private String authorId;
#Column(name = "book_id")
private String bookId;
// Constructors
// Getters and Setters
}
And change the AuthorBook table as
#Entity
public class AuthorBook {
#EmbeddedId private AuthorBookId id = new AuthorBookId();
#ManyToOne
#MapsId("user_id")
#JsonIgnore
private Author author;
#ManyToOne
#MapsId("book_id")
#JsonIgnore
private Book book;
#Column(name = "isMainAuthor")
private boolean isMainAuthor;
// getters and setter
}

Spring boot/Spring data jpa - how to update related entity?

I have following entities:
#Entity
#Table(name = "profile")
public class Profile {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToOne(cascade = CascadeType.ALL)
private ProfileContacts profileContacts;
...
}
and
#Entity
#Table(name = "profile_contacts")
public class ProfileContacts {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "description")
private String description;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
}
I am trying to update it by sending this JSON with update to REST controller:
{
"id": 1,
"description": "an update",
"profileContacts": {
"firstName": "John",
"lastName": "Doe"
}
}
so in the end it calls
profileRepository.save(profile);
where profileRepository is instance of ProfileRepository class:
public interface ProfileRepository extends JpaRepository<Profile, Long> {
}
which is spring-data-jpa interface.
But each time after such update it updates profile table but adds new row to profile_contacts table (table which corresponds to ProfileContactsentity) instead of updating existing ones.
How can I achieve updating?
As per your JSON structure. Yes it will create new profileContacts entry for every time.
The problem every time while saving profile entity you are passing "id": 1 that means Hibernate can identify the entity by this id value (primary key) but for profileContacts mapping you are not sending the id that's why Hibernate considering it has a new entity every time.
To update your profileContacts entity make sure to pass the id of it.
Example:
{
"id": 1,
"description": "an update",
"profileContacts": {
"id" : yourEntityId
"firstName": "John",
"lastName": "Doe"
}
}
Well, that's the expected behavior.
You're not telling hibernate to update the profileContacts.
For the framework to be able to update it, you need to send the profileContact's primary key - which in your case is the ProfileContacts#id.
Something like this:
{
"id": 1,
"description": "an update",
"profileContacts": {
"id": 1
"firstName": "John",
"lastName": "Doe"
}
}
Need to specify the join column in the parent Entity.
#Entity
#Table(name = "profile")
public class Profile {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToOne(cascade = CascadeType.ALL)
**#JoinColumn(name = "id")** //
private ProfileContacts profileContacts;
...
}
Now when you try to save Profile entity it will save the child entity also.
And also needs to include Id in jason request for child entity also
{
"id": 1,
"description": "an update",
"profileContacts": {
"id": 1,
"firstName": "John",
"lastName": "Doe"
}
}
Ok, I see the problem. As #Matheus Cirillo pointed out, you need to tell the hibernate to update the row.
Now, how do you tell the hibernate to update a row - By providing the primary key of the existing row.
But, creating an object with the primary key set is not enough. You need that entity class to be attached to the entity manager and the persistence context.
You can have something like,
//This attaches the entity to the entity manager
ProfileContacts existingProfileContacts = profileContactRepository.getOne(2);
Profile profile = new Profile();
....
....
profile.setProfileContacts(existingProfileContacts);
profileRepository.save(profile);
I hope this helps.

PUT operation in spring data rest

I have the model Person with relations to Account and School models:
#Entity
public class Person {
#GeneratedValue
#Id
private Long id;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#RestResource(exported = false)
private Account account;
#ManyToOne(fetch = FetchType.EAGER)
private School school;
}
The idea - is that account should be editable only with user model. For the school in my requests I'm sending the url something like that:
"school": "http://localhost:8080/schools/2"
It's working ok for POST requests. I'm sending json like that:
{
"account": {
"username": "test",
"email": "testEmail"
},
"school": "http://localhost:8080/schools/2"
}
After that request new person and account are created, for school_id - I have needed value in database.
It's working ok for PATCH requests. I'm sending json like that:
{
"account": {
"username": "new test",
"email": "new testEmail"
},
"school": "http://localhost:8080/schools/1"
}
After that request needed fields are updated in database.
The issue in PUT request. I'm sending json like that:
{
"account": {
"username": "put test",
"email": "put testEmail"
},
"school": "http://localhost:8080/schools/2"
}
The result of this request:
in accounts table I have new row, old account row is not updated and
not deleted;
school is not updated.
I want to have the same behavior for PUT operations as I have for PATCH request. I need to update needed fields by PUT request. I need a help. What should I change?

Java Rest API - resource/data separation with JPA

i'm currently building a rest-api with Java-EE/Jax-RS/JPA.
I already have a working database + database model and used the eclipse option for creating jpa-entities from existing tables.
I've created some basic services and i'm curious now whether the use of jpa-associations makes sense for building a rest-api or not because it sometimes leads to a big chunk of data getting exposed. I'm also unsure about where to separate the data that is being exposed.
E.g:
table "FOLDER" has an id, name
table "FOLDER_ITEM" has an id, folder_id (fk), item_id (fk)
table "ITEM" has an id, name, itemprop_id(fk)
table "ITEM_PROP" has an id, valueA, valueB, valueC
Calling /folders/1 currently outputs:
{
"id": 1,
"name": "Folder1",
"items": [
{
"id": 1,
"name": "pencil",
"item_prop": {
"id": "1",
"valueA": "example",
"valueB": "example",
"valueC": "example"
}
},...]
}
which adds up to a lot of data if there are a lot of items connected to a folder.
So i thought it maybe simpler and cleaner to separate the data by creating a service /items/{id} which would give me only one item at a time.
But in this case i would also have to create a service for getting the items of a folder. E.g /items/?withFolderId=1
or even /folders/1/items. I see the following options:
1) use jpa-associations but mark the list of items (inside the folder-class) as json-ignored for /folders/1 and force the output of the items when calling /folders/1/items.
2) write queries on my own
in case of the latter i'm asking my self "why would i even use jpa at all?"
While beeing confused by this i might also have to say that all my tables have a foreign key to a user-id.
So i usually only want to get the folders of a specific user (the user who is currently logged in) by creating a service /users/1/folders and at this point where would i separate the data ?
I could serve my entire client from the /users/ endpoint which would lead to the same problem as above. And now we can definitely talk about a big chunk of data getting exposed, depending on how much folders a user has and how much items the folders contain.
{
"id":1
"name":"testuser"
"password":"PW"
"folders":
[{
"id": 1,
"name": "Folder1",
"items": [
{
"id": 1,
"name": "pencil",
"item_prop": {
"id": "1",
"valueA": "example",
"valueB": "example",
"valueC": "example"
}
},....]
},....]
}
I feel like i'm distracting myself a lot with this problem. Are there any suggestions or common ways to solve this problem ?
if you are using jackson library then you can mention #JsonIgnore to avoid json response. Following Example code.
#Entity
#Table(name="address")
#EqualsAndHashCode(exclude={"id","companies","clientDetails"})
#Getter
#Setter
#NoArgsConstructor
#ToString(exclude = {"companies","clientDetails"})
#JsonIgnoreProperties(ignoreUnknown = true)
public class Address implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(unique=true, nullable=false)
private Integer id;
#Column(name="address_line1")
private String addressLine1;
#Column(name="address_line2")
private String addressLine2;
#Column
private String city;
#Column
private String country;
#Column(name="phone_no")
private String phoneNo;
#Column(name="postal_code")
private String postalCode;
#Column
private String state;
//bi-directional many-to-one association to ClientDetail
#OneToMany(mappedBy="addressBean",fetch=FetchType.LAZY)
#JsonIgnore
private Set<ClientDetail> clientDetails;
//bi-directional many-to-one association to Company
#OneToMany(mappedBy="address", fetch=FetchType.LAZY)
#JsonIgnore
private Set<Company> companies;
}

Spring data rest merge patch

I am using spring data rest in my project and have question about
application/merge-patch+json request.
While application/json-patch+json request works good i have troubles with merge-patch.
For example i have nested object without repository like this
"student":{
"id":1,
"address":{
"id":1,
"street":2,
"building":2
}
}
And i am sending PATCH, application/merge-patch+json to students/1
with this payload
{
"address":{
"street":3
}
}
I am getting this result
"student":{
"id":1,
"address":{
"id":2,
"street":3,
"building":null
}
}
So spring data rest just created new address object instead of merging.
Java code is like this
#Entity
#Table(name = "Student")
public class Student {
#Id
#GeneratedValue
private long studentId;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "addressId")
private Address address;
//getters and setters
}
Address class:
#Entity
#Table(name = "Address")
public class Address {
#Id
#GeneratedValue
private long addressId;
private String street;
private String building;
//getters and setters
}
Also student have rest repository and address have not
My question is how i can achieve correct behavior in merging patch requests in spring data rest?
Though it is delayed response, it can be helpful to others who has same issue
In the input JSON, address id is missing.
{
"address":{
"id" : 1,
"street":3
}
}

Categories

Resources