Java Rest API - resource/data separation with JPA - java

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;
}

Related

Hide ManyToOne field in REST API in Spring Boot

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.

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
}

Mapping Multiple Columns to same field of an Object which is part of an Array JPA

I am trying to map an array of Objects to a field. All the fields in that object are being mapped to columns with different name but similar structure. The response structure should be:
"customers": [
{
"firstName": "string",
"lastName": "string",
"products": [
{
"description":"string",
"amount": "string"
},
{
"description":"string",
"amount": "string"
}
]
}
]
Inside the products field, I have a list of product(description and amount). In DB, columns are stored like
product_des1,product_amt1,product_des2,product_amt2.....product_des30,product_amt30
. I need to map these two fields to the product(object). How should I approach to solve the problem using JPA annotations if possible?
For the reference:
Customers.class
#Entity
public class Customers implements Serializable {
#Column(name = "firstName")
private String firstName;
#Column(name = "lastName")
private String lastName;
#ElementCollection
List<Products> products;
}
Product.class
#Embeddable
public class Product implements Serializable {
#Column(?)
private String description;
#Column(?)
private String amount;
}
Inside the products field, I have a list of product(description and amount). In DB, columns are stored like
product_des1,product_amt1,product_des2,product_amt2.....product_des30,product_amt30
So your Products JPA entity should simply look like this:
#Embeddable
public class Products implements Serializable {
#Column(name = "product_des1")
private String description1;
#Column(name = "product_amt1")
private String amount1;
#Column(name = "product_des2")
private String description2;
#Column(name = "product_amt2")
private String amount2;
// ... repeat
}
if you don't want to do additional mapping between the DB and JPA entities (which I don't recommend - I try to keep JPA entities as exact representation of a DB row and map, if necessary, in Java and not between different technologies).

Alternative way to enforce foreign key constraint using spring boot and hibernate

Is there any alternative ways to enforce foreign key constraints with JPA, Hibernate, etc. to minimize to data stored against a POJO?
I have a spring boot project with User and Form Model, Dao, and Controller classes that is connected to a MySQL database. A User can have many Forms but a Form can only have one User. So the relationship is one to many. To enforce the foreign key constraint I use the #OneToMany annotation with a List. With this design, when I use an HTTP GET method in postman, a single user returns the user AND all his/her associated forms, which I don't like. Is there another way to enforce the a foreign key constraint such that I don't have to store all the information against the User class, but ensure referential integrity within the database?
Below is my User Class. At the bottom is where I create a List of type Form and annotate it with the #OneToMany annotation and then tell it which attribute in the form class to join it with using the #JoinColumn
package org.hoa.HOA_Website.WebsiteDatabaseAPI.Entities;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import java.util.List;
#Entity
#Table
public class User {
#Id
#GeneratedValue
#Column(name = "user_id", insertable = false, updatable = false)
private Long userID;
#NotBlank
#Column(length = 30, nullable = false)
private String firstName;
#NotBlank
#Column(length = 30, nullable = false)
private String lastName;
#NotBlank
#Column(length = 30)
private String middleName;
#NotBlank
#Column(length = 14)
private String phoneNumber;
#NotBlank
#Column(length = 50, unique = true, nullable = false)
private String email;
#Column(nullable = false)
private Boolean admin;
private int age;
#NotBlank
#Column(length = 100, nullable = false)
private String passwordHash;
#OneToMany(targetEntity = Form.class, cascade = CascadeType.ALL)
#JoinColumn(name = "submitter_id")
private List<Form> formsMade;
/*
Getters and Setters Below
*/
Now below here is the Form Class. Above submitter I have the column named such that it will match the #JoinColumn annotation. This ensures that all submitter ids sent via a POST method having a matching user id.
package org.hoa.HOA_Website.WebsiteDatabaseAPI.Entities;
import javax.persistence.*;
import java.util.Date;
#Entity
#Table
public class Form {
#Id
#GeneratedValue
private Long formID;
#Column(nullable = false)
private Date dateSubmitted;
#Column(name = "submitter_id", nullable = false)
private Long submitter;
#Column(nullable = false)
private Boolean approvalStatus;
#Column(length = 30, nullable = false)
private String formType;
/*
Getters and Setters Below
*/
BUT, when i perform a GET method to find all the Users. I get all the Users AND their associated Forms.
[
{
"userID": 1,
"firstName": "Jane",
"lastName": "Doe",
"middleName": "Halyard",
"phoneNumber": "1234567890",
"email": "janedoe#gmail.com",
"admin": false,
"age": 20,
"passwordHash": "klsjfougalsdg2e98y54e982ajsdng924uierg",
"formsMade": [
{
"formID": 5,
"dateSubmitted": "2019-06-11T12:25:43.000+0000",
"submitter": 1,
"approvalStatus": false,
"formType": "ConstructionForm"
},
{
"formID": 6,
"dateSubmitted": "2019-06-11T12:25:43.000+0000",
"submitter": 1,
"approvalStatus": false,
"formType": "HardshipForm"
}
]
},
{
"userID": 2,
"firstName": "John",
"lastName": "Doe",
"middleName": "Michael",
"phoneNumber": "0987654321",
"email": "johndoe#gmail.com",
"admin": true,
"age": 32,
"passwordHash": "klsjfougalsdg2e98y5423bt49sdgajsdng924uierg",
"formsMade": [
{
"formID": 3,
"dateSubmitted": "2019-06-11T12:25:43.000+0000",
"submitter": 2,
"approvalStatus": false,
"formType": "HardshipForm"
}
]
}
]
So finally, is there anyway to ensure referential integrity but without storing so much on the User? Hopefully more like relational data structures and just store the foreign key on the Form?
I think what you are after is a DTO.
Is there any alternative ways to enforce foreign key constraints with JPA, Hibernate, etc. to minimize to data stored against a POJO?
Discussing about a POJO is a bit inaccurate. Your Entities define ORM and those should be defined as they are. You should think about what is the data you return. Your entiteis do not actually store the data but you populate them with all the data and return all that data if you query and return them as they are, so using them also as DTOs.
It seems that you return Collection<User> (so entities) as a response.
Instead you should return a collection of DTOs - say - Collection<SimpleUserDTO> where DTO could be like:
public class SimpleUserDTO {
public Long userID;
public String firstName;
public String lastName;
public String middleName;
public String phoneNumber;
public String email;
public Boolean admin;
public int age;
public String passwordHash;
// NOTE: no formsMade here!
}
So your Controller method should call Service method that returns this collection of DTOs that it obtains from some Repository method either already mapped as DTOs or as entities that Service method then maps one by one to be a DTO.
In your Service method you could map entity values to DTO - field by field or perhaps with a library like ModelMapper - but best way in my opinion would be that you implemented a Repository method ( with a Projection or a Tuple query) that populates this DTO straight from the query and lets the Service method call that.
And after all this very ugly but easy way to achive this same would just loop the users in your Controller and set the formsMade to null but it is finally a very inefficient solutions and will not be a flexible way to hanled all this conversion stuff later with different results etc...
There is a lot of stuff to explain more in details but I hope that with this information you might be able to find more information from SO or elsewhere in relevant documentation.
And: of course you need to map back from DTO to entity then when passing data to Controller.

Not save an enum value in the database, why?

Does not save an enum value in the database, I think that's it, or I do not understand, I try to save since the console shows me a data, but when I ask from the database I get another value
#Entity
#Table(name = "User")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name = "name")
private String name;
#Column(name = "password")
private String password;
#Column(unique = true, name = "email")
private String email;
#Column(name="rol")
Rol rol;
#Column(name = "life")
Life life;
public User() {
}
}
i have this in the .info, show this message "OWNER"
Set<User> users =new HashSet<>();
log.info("CONSOLE"+ user.getRol());
users.add(user);
meet.setUsers(users);
return meetRepository.save(meet);
but in swagger i get other value
ROL: PARTICIPANT
[
{
"id": 1,
"name": "string2",
"state": "pause",
"bet": {
"profit": 0,
"inversion": 0
},
"users": [
{
"id": 1,
"name": "string",
"password": "string",
"email": "ema",
"rol": "PARTICIPANT",
"life": "suspend"
}
]
}
]
If your fields rol and life are Enums you have to declare them as Enums with #Enumerated. There are two options. Default will store the ordinal number. Or you can choose to use the string name to store in the DB. That's the better option in terms of maintainability:
#Enumerated(EnumType.STRING)
#Column(name="rol")
Rol rol;
#Enumerated(EnumType.STRING)
#Column(name = "life")
Life life;
Two remarks:
When the database field has the same name as the attribute you can omit #Column. And if the table has the same name as the entity this is also true for the #Table annotatoin.
Read more about Enums and JPA and if you really should use it in one of my articles: https://72.services/de/should-you-use-enums-with-jpa/
When you save a Enum to the database as stated above you have the option of saving it as String or as Numerical Ordinal. The numerical option is very painfull because every time you need to update your enum values or change the order of the values you will mess with your database options for the field. Also another thing that is very painfull even when you store the strings on the database is that when you are using some ORM such as Hibernate and you have NULL values. So keep your enums as strings and avoid NULL value.

Categories

Resources