How to post ManyToOne entity using JSON format - json

I've two entities issue and user I would like to assign the user to an issue but when trying to do that using this JSON the user_id in the issues table is NULL.
#Data
#Entity
#Table(name = "issues")
public class Issue {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String number;
#Column
private String title;
#Column
private String description;
#Column
#Enumerated(EnumType.STRING)
private State state;
#JsonIgnore
#CreationTimestamp
#Column
private Timestamp createDate;
#JsonIgnore
#UpdateTimestamp
#Column
private Timestamp modifyDate;
#ManyToOne(targetEntity = User.class)
#JoinColumn
private User user;
public Issue() {
}
#Data
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String fullName;
#Column
private String username;
#Column
private String email;
#Column
private String password;
#Column
#Enumerated(EnumType.STRING)
private Role role;
#JsonIgnore
#CreationTimestamp
#Column
private Timestamp createDate;
#JsonIgnore
#UpdateTimestamp
#Column
private Timestamp modifyDate;
public User() {
}
public User(String fullName, String username, String email, String password, Role role) {
this.fullName = fullName;
this.username = username;
this.email = email;
this.password = password;
this.role = role;
}
Firstly I've created an user without any issues, but do not know how to manage with the issue here is the JSON I am using via postman.
{
"number": "3",
"title": "Create an working service",
"description": "The problem is that we do not have an working service.",
"state": "NEW",
"user_id": "1"
}
This part is responsible for saving the issue.
public void save(Issue issue) {
if (issue == null)
return;
Issue actual = issueRepository.findByNumber(issue.getNumber());
if (actual != null) {
actual.setNumber(issue.getNumber());
actual.setTitle(issue.getTitle());
actual.setDescription(issue.getDescription());
actual.setState(issue.getState());
actual.setUser(issue.getUser());
issueRepository.save(actual);
} else {
issueRepository.save(issue);
}
}
In the controller I just have #Valid #RequestBody Issue issue and the service saves the issue.

What you're sending as JSON is not an Issue.
First because it does not represent a database-persistent issue (and that's what the Issue class is for), but rather the JSON structure the public API expects from its clients.
Second because an Issue doesn't have any field named user_id, but your JSON does. So use a different class than Issue, which actually matches with the JSON structure that the API expects, and thus has a user_id property.
Then use this user_id to find the User by its ID using the UserRepository, and set that User into the Issue you're creating.
I would rename user_id to userId to respect the Java conventions, too.

You need to post json like this.
{
"number": "3",
"title": "Create an working service",
"description": "The problem is that we do not have an working service.",
"state": "NEW",
"user":{
"id":1
}
}
Here issue have join column of user so with user entity you have to pass userid.
Your save would be like this.
public void save(Issue issue) {
if (issue == null)
return;
Issue actual = issueRepository.findByNumber(issue.getNumber());
if (actual != null) {
actual.setNumber(issue.getNumber());
actual.setTitle(issue.getTitle());
actual.setDescription(issue.getDescription());
actual.setState(issue.getState());
actual.setUser(userRepositoy.findById(issue.getUser().getId()));
issueRepository.save(actual);
} else {
issueRepository.save(issue);
}
}

user_id key was not presented in your Item class.
As per entity mapping,
Please change your JSON to this.
{
"number": "3",
"title": "Create an working service", "description": "The problem is that we do not have an working service.",
"state": "NEW",
"user":{
"fullName":"USER",
"email":"user#gmail.com",
"username":"user"
}
}

Related

Spring boot & JSON: How to add foreign key when using POST request

I can't seem to figure out how to add an entity that has a foreign key, though JSON.
I have a user model, and a post model. A user can make different posts on a website.
This is a many-to-one relationship. A user can have several posts, while a post can only have one user (the poster). The post as a foreign key representing the id of the user that made the post.
This is the User model:
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
#Table(name = "user")
public class User {
//ID
#Id
#SequenceGenerator(
name = "user_sequence",
sequenceName = "user_sequence",
allocationSize = 1
)
#GeneratedValue(
strategy = GenerationType.IDENTITY,
generator = "user_generator"
)
#Column(name = "id",nullable = false)
private int id;
private String username;
private String password;
private String email;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd")
#Column(name = "creation_date")
private Date creationDate;
//RELATIONSHIP
#OneToMany(cascade = CascadeType.ALL, mappedBy = "user")
private List<Post> posts = new ArrayList<>();
/* =========== GETTERS AND SETTERS ===========*/
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Date getCreationDate() {
return creationDate;
}
public void setCreationDate(Date creationDate) {
this.creationDate = creationDate;
}
public List<Post> getPosts() {
return posts;
}
public void setPosts(List<Post> posts) {
this.posts = posts;
}
}
This is the Post model:
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
#Table(name = "post")
public class Post {
//ID
#Id
#SequenceGenerator(
name = "post_sequence",
sequenceName = "post_sequence",
allocationSize = 1
)
#GeneratedValue(
strategy = GenerationType.IDENTITY,
generator = "post_generator"
)
#Column(name = "id", nullable = false)
private int id;
#Column(name = "post_content")
private String postContent;
private String title;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd")
#Column(name = "creation_date")
private Date creationDate;
//RELATIONSHIP
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "user_id", referencedColumnName = "id")
private User user;
/* ======== GETTERS AND SETTERS ======== */
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getPostContent() {
return postContent;
}
public void setPostContent(String postContent) {
this.postContent = postContent;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Date getCreationDate() {
return creationDate;
}
public void setCreationDate(Date creationDate) {
this.creationDate = creationDate;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
This is the postController:
#RestController
public class PostController {
#Autowired
private PostService postService;
#PostMapping("/savePost")
public Post getPost(#Validated #RequestBody Post post) {
return postService.savePost(post);
}
#GetMapping("getPost/{id}")
public Post getPost(#PathVariable int id) {
return postService.getPost(id);
}
#PutMapping("/deletePost/{id}")
public void deletePost(int id) {
postService.deletePost(id);
}
}
This is the JSON I send in to add a post. Request to: http://localhost:8080/savePost JSON body:
{
"postContent": "some content",
"creationDate": "2022-07-31",
"title": "my title",
"user": 1
}
But in postMan i get this error:
{
"timestamp": "2022-08-02T10:40:11.794+00:00",
"status": 400,
"error": "Bad Request",
"path": "/savePost"
}
And in spring i get this error: JSON parse error: Cannot construct instance of x.model.User (although at least one Creator exists): no int/Int-argument constructor/factory method to deserialize from Number value (1);
If i send in a JSON where I call the user for "user_id" or "uderId", then Im able to send the request, but then the foreign key turns into null
{
"creationDate": "2022-07-31",
"postContent": "some content",
"title": "my title",
"user_id": 1
}
what gets sent in:
{
"id": 2,
"postContent": "some content",
"title": "my title",
"creationDate": "2022-07-31",
"user": null
}
Does anyone know what im doing wrong?
Firstly, your APIs are not correct in terms of REST concept.
Here is a nice explanation.
You better should rework it to deal with Post entities:
add a userId param to the controller's value and remove it from models.
Use different classes for transport and business processes. It'll give you at least two prefenecies: first one is ability to pass any extra data through model objects, or wider, control which properties can be passed to be inserted/updated (as well as possibility of a separated validation for post/put operations), and the second one is guarantee that you won't face an Open session in View problem.
#RestController("/user/{userId}/post")
public class PostController {
#Autowired
private PostService postService;
#PostMapping("/save")
public PostResponseDTO addPost(#Validated #RequestBody PostAddDTO postModel, #PathVariable Long userId) {
return postService.savePost(userId, postModel);
}
#PutMapping("/{id}")
public PostResponseDTO updatePost(#Validated #RequestBody PostUpdateDTO postModel, #PathVariable Long userId, #PathVariable Long id) {
return postService.updatePost(userId, postModel);
}
#GetMapping("/{id}")
public PostResponseDTO getPost(#PathVariable Long userId, #PathVariable Long id) {
return postService.getPost(userId, id);
}
#DeleteMapping("/{id}")
public void deletePost(Long userId, Long id) {
postService.deletePost(userId, id);
}
}
You must:
add parameter fetch = FetchType.LAZY to both of #OneToMany and #ManyToOne declarations;
add a userId property to the PostUpdateDTO (if changing of post's owner is allowed)
At the service layer you can:
if POST: find a User by userId, validate weither exists it and probably raise some exception if doesn't or create and persist a new Post entity:
#Transactional
public PostResponseDTO addPost(PostAddDTO postModel, Long userId) {
User user = getValidUser(userId);
Post post = new Post(postModel);
UserDTO userDTO = new UserDTO(user); // here copy only simple properties, not the list of user's posts
post.setUser(user);
postRepository.save(post);
PostAddDTO result = new PostAddDTO(post);
result.setUser(userDTO);
return result;
}
/**
- will be used in both post and put operations
- #param userId user id from a controller
- #return {#link User} entity if found
- #throws RuntimeException if the entity has not been found
*/
private User getValidUser(Long userId) {
Optional<User> userOpt = userRepository.findById(userId);
if (!userOpt.isPresent()) {
log.WARN("addPost. user with id={userId} not found!", userId);
throw RuntimeException("some exception"); //!!!do not place any business info such as "user with id={userId} not found" because of a scam risc reasons
}
return userOpt.get();
}
if PUT: find a Post entity by userId and id, validate weither exists it or not and implement preferable logic. I don't know is it allowed to reassign a user? If so, check new user's existance first.
If DELETE, you may raise an exceptions in case of entities absence, but many not and just do nothing to success response be sent
One more reason why we do use transport objects. If you let it be as it is, it'll lead to an infinite loop while serialization: post.user -> (post: user.posts) {post.user -> ...}.
Of course all of this stuff is not the only way of solving of this problem and it doesn't answer all the questions about Java Persistance API, but this is the way that I went for some time ago within a concrete project.
Here is a REST guide made by a Spring team

How to include null values to JSON responses in spring boot REST API

I'm in the process of developing a RESTful API. I want to get user details by calling API. But my expected response not included null values.
Expected Response
{
"id": 1,
"username": "admin",
"fullName": "Geeth Gamage",
"userRole": "ADMIN",
"empNo": null
}
Actual Response
{
"id": 1,
"username": "admin",
"fullName": "Geeth Gamage",
"userRole": "ADMIN"
}
My Spring boot rest API code is below. why not include null parameters for my response?
#GetMapping(value = "/{userCode}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> findUser(#PathVariable String userCode)
{
return ResponseEntity.status(HttpStatus.OK).body(userService.getUserByUserName(userCode));
}
#Override
#Transactional
public Object getUserByUserName(String username)
{
try {
User user = Optional.ofNullable(userRepository.findByUsername(username))
.orElseThrow(() -> new ObjectNotFoundException("User Not Found"));
return new ModelMapper().map(user, UserDTO.class);
} catch (ObjectNotFoundException ex) {
log.error("Exception : ", ex);
throw ex;
} catch (Exception ex) {
log.error("Exception : ", ex);
throw ex;
}
}
User entity class and UserDTO object class as below
User.class
#Data
#Entity
#Table(name = "user")
public class User{
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
private Long id;
#Column(name = "USERNAME", nullable = false, length = 64)
private String username;
#Column(name = "USER_ROLE", nullable = false)
private String userRole;
#Column(name = "EMP_NO")
private String empNo;
}
UserDTO.class
#Data
public class UserDTO {
private Long id;
private String username;
private String fullName;
private String userRole;
private String empNo;
}
Assuming you are using Jackson you can configure its global behaviour using setSerializationInclusion(JsonInclude.Include) on the ObjectMapper.
For your use case you can configure it as NON_EMPTY or ALWAYS. For more details please check https://fasterxml.github.io/jackson-annotations/javadoc/2.6/com/fasterxml/jackson/annotation/JsonInclude.Include.html.
You can also do this at class or attribute level using the corresponding annotation #JsonInclude.

Duplicate entry instead of using existing entity

I'm getting MySQLIntegrityConstraintViolationException while trying to add new Product. Each Product has Category which has unique value name. I'm getting this exception when I try to add new Product with already existing Category. Example below:
POST 1
{
"name" : "apple",
"categoryName" : "fruit"
}
Response
{
"name": "apple",
"categoryName": "fruit",
"kcal": null
}
Post 2:
{
"name" : "banana",
"categoryName" : "fruit"
}
Response:
{
"timestamp": 1533451793052,
"status": 500,
"error": "Internal Server Error",
"exception": "org.springframework.dao.DataIntegrityViolationException",
"message": "could not execute statement; SQL [n/a]; constraint [UK_8f25rdca1qev4kqtyrxwsx0k8]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement",
"path": "/product/add"
}
Which is obviously not what I'd expect, instead I want banana to use same category as apple.
Ok, the code, first entities and dto's
#Entity
#Table(name = "tbl_product")
public class Product implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Double kcal;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn
private Category category;
}
public class ProductDto {
private String name;
private String categoryName;
private Double kcal;
}
#Entity
#Table(name = "tbl_category")
public class Category implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true)
private String name;
public Category(){ }
public Category(String name){
this.name = name;
}
}
Post from controller
#PostMapping("/add")
public ProductDto addProduct(#Valid #RequestBody ProductDto productDto){
Product product = productRepository.save(dtoToEntityTranslator.translate(productDto));
return entityToDtoTranslator.translate(product);
}
And pretty straight-forward translators
public class DtoToEntityTranslator {
public Product translate(ProductDto productDto){
Product product = new Product();
product.setName(productDto.getName());
product.setCategory(new Category(productDto.getCategoryName()));
product.setKcal(productDto.getKcal());
return product;
}
}
public class EntityToDtoTranslator {
public ProductDto translate(Product product){
ProductDto productDto = new ProductDto();
productDto.setName(product.getName());
if(product.getCategory() != null) {
productDto.setCategoryName(product.getCategory().getName());
}
productDto.setKcal(product.getKcal());
return productDto;
}
}
Not sure if it's worth mention, my repository for Product
#Repository
public interface ProductRepository extends CrudRepository<Product, Long> {
}
The error is caused by this line
product.setCategory(new Category(productDto.getCategoryName()));
You are tolding Hibernate that this is a new Category because category name is not the Id for Category.
To solve this, you can get the Category with the provided and set to the Category.
Another way is that for existing Category, instead of sending the name to server, you can consider sending the category id.

Prevent field of JSON object to be excluded, while serializing one-to-many relationship in springboot

i have 2 classes , one for User details and other for User fitness
all i want is to exclude name field from final json object of FitnessRecord class using FitnessRecordRepository.findAll().
this is my json output now.
{
"fitness_id": 1,
"date_of_performance": "2018-04-06",
"user": {
"user_id": 1,
"name": "Hilal Kaldane",
"email": "hilalkaldane#gmail.com"
}
}
and what i want is
{
"fitness_id": 1,
"date_of_performance": "2018-04-06",
"user": {
"user_id": 1,
"email": "hilalkaldane#gmail.com"
}
}
User.class
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer user_id;
#NotNull
private String name;
#NotNull
#Column(unique = true)
private String email;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "user")
private Set<FitnessRecord> fitness_record = new HashSet<>();
//Getter and Setters are omitted
}
FitnessRecord.class
public class FitnessRecord {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer fitness_id;
public Integer getFitness_id() {
return fitness_id;
}
public void setFitness_id(Integer fitness_id) {
this.fitness_id = fitness_id;
}
#JoinColumn(name="user_id")
#ManyToOne
private User user;
#Temporal(TemporalType.DATE)
private Date date_of_performance;
}
Note:- I dont want to use JSON ignore on User Class, since i will be using the name field in other places such as user information retrieval

Why JPA Hibernate changes the name of the field?

In model class I have isActive field with is boolean, that represent the is_active field in MySql DB. Here is whole model class:
package ca.gatin.model;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
#Entity
#Table(name = "Account")
public class Account {
#Id
#GeneratedValue
private Long id;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(nullable = false, unique = true)
private String email;
#Column(nullable = false)
private String password;
#Column(name = "is_active", nullable = false)
private boolean isActive;
#Column(name = "date_created")
private Date dateCreated;
#Column(name = "date_last_modified")
private Date dateLastModified;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public boolean isActive() {
return isActive;
}
public void setActive(boolean isActive) {
this.isActive = isActive;
}
public Date getDateCreated() {
return dateCreated;
}
public void setDateCreated(Date dateCreated) {
this.dateCreated = dateCreated;
}
public Date getDateLastModified() {
return dateLastModified;
}
public void setDateLastModified(Date dateLastModified) {
this.dateLastModified = dateLastModified;
}
}
But when I fetch account let's say through REST API like:
#RequestMapping(
value = "/{id}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public ServiceResponse<Account> getAll(#PathVariable("id") Long id) {
ServiceResponse<Account> serviceResponse = accountService.getAccountById(id);
return serviceResponse;
}
In a reply object I get isActive field renamed by Hibernate to "active" like this:
{
"id": 19,
"firstName": "Julia",
"lastName": "Sarandi",
"email": "julia#gatin.ca",
"password": "111111",
"dateCreated": 1451293826000,
"dateLastModified": null,
"active": true
}
Why? Why all other field's names stay same as in Account class, but isActive is renamed?
That is one question, and another question is:
I am new in Hibernate, and I do no understand why in logs of Hibernate DB requests is shows some weird queries:
Hibernate: select account0_.id as id1_0_0_, account0_.date_created as date_cre2_0_0_, account0_.date_last_modified as date_las3_0_0_, account0_.email as email4_0_0_, account0_.first_name as first_na5_0_0_, account0_.is_active as is_activ6_0_0_, account0_.last_name as last_nam7_0_0_, account0_.password as password8_0_0_ from Account account0_ where account0_.id=?
What query language is it? What are symbols: "0_", "0_0_". Can I switch logs to show MySQL queries to make it more understandable?
FYI
In my application.properties file I have following configuration:
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.DefaultNamingStrategy
Change getter and setter method name for isActive field as:
public boolean getIsActive() {
return isActive;
}
public void setIsActive(boolean isActive) {
this.isActive = isActive;
}
Then it return isActive in response.
That has nothing to do with Hibernate, and everything to do with your JSON marshaller. Spring uses Jackson, and Jackson uses bean properties (i.e. getters) to access the data and transform them to JSON fields. Your getter is named isActive(), and thus corresponds to a bean property named active, hence the name of the attribute in the JSON.
If you want the JSON field to be named isActive, then your getter should be isIsActive(). Or much better, you should annotate it with #JsonProperty("isActive").
To answer your second question, the query is a SQL query, generated by Hibernate. It changes the name of tables and assigns aliases to columns mainly to disambiguate tables, and fields of different tables that could have the same name, AFAIK.