I've a Java REST API with JPA. Whenever I create an entity, I also want to create another entity with a forgein key. Or maybe someone can advise me otherwise, I would really appreciate it and learn from it =)
When i successfully create a company it will make a file entity in the database as well, so that works fine. but,
Whenever I execute a findAll method in the JPA repository it will give me a loop of the one company that i've created.
like this:
If you need any more information, please let me know!
Company.class
package nl.hulpvriend.dehulpvriend.company;
import javax.validation.constraints.NotNull;
import lombok.*;
import nl.hulpvriend.dehulpvriend.file.File;
import javax.persistence.*;
import javax.validation.constraints.Size;
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
#Setter
#Getter
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#NotNull
private String email;
#Column(unique = true)
#NotNull(message = "The company name cannot be empty")
#Size(max = 30, message = "Company name cannot be longer than 30 characters")
private String name;
#NotNull(message = "Company must contain a service type")
#Enumerated(EnumType.STRING)
private ServiceType serviceType;
private double stars;
private Integer pricePerHour;
private String description;
private String kvk;
#OneToOne(mappedBy="company", cascade = CascadeType.ALL)
private File file;
}
File.class
package nl.hulpvriend.dehulpvriend.file;
import lombok.*;
import nl.hulpvriend.dehulpvriend.company.Company;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
#AllArgsConstructor
#Getter
#Setter
#NoArgsConstructor
#Entity
#Data
public class File {
#Id
private Integer id;
private String fileId;
#OneToOne(fetch = FetchType.EAGER)
#MapsId
private Company company;
#NotNull(message = "Must contain a data")
#Lob
private byte[] data;
private String downloadUrl;
private String fileName;
private String fileType;
public File(String fileName, String fileType, byte[] data) {
this.fileName = fileName;
this.fileType = fileType;
this.data = data;
}
}
Add JsonIgnore to one of the references to break the loop:
For example in the File class:
#JsonIgnore
#OneToOne(fetch = FetchType.EAGER)
#MapsId
private Company company;
Related
package com.springboot.blog.entity;
import lombok.*;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
#Data
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "posts")
public class Post {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
private Long id;
#Column(name = "title", nullable = false)
private String title;
#Column(name = "description", nullable = false)
private String description;
#Column(name = "content", nullable = false)
private String content;
#OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Comment> comments = new HashSet<>();
}
Here is the Post class
package com.springboot.blog.payload;
import lombok.Data;
import java.util.Set;
#Data
public class PostDTO {
private Long id;
private String title;
private String description;
private String content;
private Set<CommentDTO> comments;
}
Here is PostDTO class.
#Override
public PostDTO getPost(Long id) {
Post temp_post = postRepository.findById(id).
orElseThrow(() -> new ResourceNotFoundException("Post", "Id", id));
return mapToDTO(temp_post);
}
private PostDTO mapToDTO(Post post) {
return modelMapper.map(post, PostDTO.class);
}
I used mapToDTO method to convert post to postDTO.
But When I test it, the error message appeared.
{"timestamp":"2022-04-30T14:03:03.369+00:00","status":500,"error":"Internal Server Error","trace":"org.modelmapper.MappingException: ModelMapper mapping errors:\n\n1) Error mapping com.springboot.blog.entity.Post to com.springboot.blog.payload.PostDTO\n\n1 error\n\tat org.modelmapper.internal.Errors.throwMappingExceptionIfErrorsExist(Errors.java:380)\n\tat org.modelmapper.internal.
========================================================================================
** But when I replace the #Data annotation in class Post with #Getter, #Setter. It works perfectly! **
Well I have a class Service and a class Employee
Service.java
import lombok.Data;
import javax.persistence.*;
#Data
#Table(name = "services")
#Entity
public class ServiceResource {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String date;
private String longitude;
private String latitude;
#ManyToOne
#JoinColumn(name="employee_id")
private EmployeeResource employee;
}
Employee.java
import lombok.Data;
import javax.persistence.*;
#Data
#Table(name = "employees")
#Entity
public class EmployeeResource {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String longitude;
private String latitude;
}
Now I'd like to build my application that the mapping between service and employee is done with JPA. Is that even possible and how?
Add this to your EmployeeResource Entity:
#OneToMany(mappedBy = "employee", cascade = CascadeType.ALL)
private List<ServiceResource> serviceList;
and in ServiceResource, modify the join column like this:
#ManyToOne
#JoinColumn(name="employee_id", referencedColumnName = "id")
private EmployeeResource employee;
The table is created successfully and filled with information in H2 database as seen here:
When using Spring boot to display this table information with JSON format i only see this:
Here you can see the code snippet from the object
package com.share.sharelt.entity;
import com.fasterxml.jackson.annotation.JsonBackReference;
import lombok.Data;
import javax.persistence.*;
import java.math.BigDecimal;
import java.util.Date;
#Entity
#Data
#Table(name = "item_rental")
public class ItemRental {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
#Column(name = "created")
private Date created;
#Column(name = "cost")
BigDecimal cost;
#Column(name = "rent_begin")
private Date rentBegin;
#Column(name = "rent_end")
private Date rentEnd;
#Column(name = "is_confirmed")
private boolean isConfirmed;
#JsonBackReference
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "renter_id", nullable = true)
private User user;
public ItemRental(){};
}
The problem is that i want to see the whole table information, more specifically the "renter_id" column
One of the solutions is to create a DTO class which is gonna be a JSON wrapper to your ItemRental entity
Something like ItemRentalDTO and UserDTO with all fields of ItemRental and User entity class
Link: https://www.baeldung.com/entity-to-and-from-dto-for-a-java-spring-application
One Group has many Users:
Group
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import javax.persistence.*;
import java.util.Collection;
import java.util.List;
#Entity
#Table(name = "GROUPS")
public class Group {
#Id
#Column(name = "ID")
private Long ID;
#Column(name = "NAME")
private String NAME;
//#JsonManagedReference
#OneToMany(mappedBy = "group"
//, fetch = FetchType.EAGER
//, cascade = CascadeType.ALL
)
private List<Users> itsUser;
//getters and setters are omitted for clarity
}
Users
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import com.fasterxml.jackson.annotation.JsonProperty;
import javax.persistence.*;
import static javax.persistence.GenerationType.SEQUENCE;
#Entity
#Table(name = "USERS")
#SequenceGenerator(name = "SEQUENCE_USER_ID", //my own name in java (unique)
sequenceName = "GENERATOR_SEQUENCE_USERS", //in database
initialValue = 1,
allocationSize = 1)
public class Users {
#JsonProperty(value = "id") //these play a role when both reading or writing
#Id
#Column(name = "ID")
#GeneratedValue(strategy=SEQUENCE, generator="SEQUENCE_USER_ID")
private Long ID;
#JsonProperty(value = "name")
#Column(name="NAME")
private String NAME;
#JsonProperty(value = "username")
#Column(name="USERNAME")
private String USERNAME;
#JsonProperty(value = "password")
#Column(name="PASSWORD")
private String PASSWORD;
#JsonProperty(value = "email")
#Column(name="EMAIL")
private String EMAIL;
#JsonProperty(value = "picture") //Now it works with both mypic and picture as json keys
#Column(name="PICTURE")
private String PICTURE;
//#Column(name="GROUP_ID") //we already have a ManyToOne for this, we cannot repeat it
#JsonProperty(value = "groups_id")
//to ignore it in jpa (http://stackoverflow.com/questions/1281952/jpa-fastest-way-to-ignore-a-field-during-persistence)
private Long itsGroupId;
#Transient
public Long getItsGroupId() {
if(itsGroupId == null) {
this.itsGroupId = group.getID();
} else {
//nop
}
return itsGroupId;
}
public void setItsGroupId(Long itsGroupId) {
this.itsGroupId = itsGroupId;
}
//#JsonIgnore
//#JsonProperty(value = "groups_id")
//#JsonBackReference
#ManyToOne(optional = false, targetEntity = Group.class)
#JoinColumn(
name = "GROUP_ID", //column name
referencedColumnName = "ID" //reference name
)
private Group group;
//getters and setters are omitted for clarity
}
We are using Spring with Spring-data and Jackson to do things automagically but we cannot configure the magic:
We are trying to stick on the following constraints at the same time:
1) Keep the ability to have a reference to the groupId and the ManyToOne relationship group.
This is easy to be achieved by putting #Transient annotation at the groupId because #Column is not allowed since we have already declared the #ManyToOne annotation. You also have to implement the getGroupId method accordingly.
2) Return a json of Users class that contains the groups_id.
This can be implemented by setting the #JsonProperty annotation.
3) Create a user class, and also save it in the database, by a json. The json contains groups_id which has as a value an integer for the foreign key.
This does not work because by setting it #Transient above, then the system refuses to save in the database something that is transient or at least this is how we interpret this exception:
HTTP Status 500 - Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: not-null
property references a null or transient value: com.pligor.mvctest.models.Users.group;
nested exception is org.hibernate.PropertyValueException:
not-null property references a null or transient value: com.pligor.mvctest.models.Users.group
On the backend do something like this:
Group group = groupRepository.findById(userResource.getGroupId());
if (group != null) {
User user = new User(userResource);
user.setGroup(group);
userRepository.save();
}
The idea behind this is that you need to fetch the group from the DB, to be able to link it with the newly created User
I am trying to join three entities (table) using spring-jpa into one table using Many-To-Many relationship.
Three classes are :
1] User
2] Resource
3] Privilege
And I want to combine these three entities into one User_Resource_Privilege table
User Entity
package com.****.acl.domain;
import java.util.ArrayList;
import java.util.Collection;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
#Entity
public class User {
#Id #GeneratedValue(generator="system-uuid")
#GenericGenerator(name="system-uuid", strategy = "uuid")
#Column(name="user_id", nullable=false, length=40)
private String userId;
#Column(name="user_name", nullable=false, length=45)
private String userName;
#Column(name="first_name", nullable=true, length=45)
private String firstName;
#Column(name="last_name", nullable=true, length=45)
private String lastName;
#Column(name="email", nullable=true, length=50)
private String email;
public User(){
}
public User(String userName, String firstName, String lastName, String email) {
this.userName = userName;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
getter and setters .......
}
Resource Entity
import java.util.ArrayList;
import java.util.Collection;
import javax.persistence.*;
import org.hibernate.annotations.GenericGenerator;
#Entity
public class Resource {
#Id #GeneratedValue(generator="system-uuid")
#GenericGenerator(name="system-uuid", strategy = "uuid")
#Column(name="resource_id", nullable=false, length=40)
private String resourceId;
#Column(name="resource_name", nullable=false, length=45)
private String name;
#Column(name="resource_type", nullable=false, length=45)
private String type;
public Resource(){
}
public Resource(String name, String type) {
this.name = name;
this.type = type;
}
getter and setter ......
}
Privilege Entity
import java.util.ArrayList;
import java.util.Collection;
import javax.persistence.*;
import org.hibernate.annotations.GenericGenerator;
#Entity
public class Privilege {
#Id #GeneratedValue(generator="system-uuid")
#GenericGenerator(name="system-uuid", strategy = "uuid")
#Column(name="privilege_id", nullable=false, length=40)
private String privilegeId;
#Column(name="resource_name", nullable=false, length=45)
private String name;
#Column(name="resource_description", nullable=true, length=45)
private String description;
public Privilege(){
}
getters and setters ....
}
Now I want to create one table by joining all the three entities described above.
The join in ER diagram:
Can someone please help me in joining these three tables using Many-To-Many relationship and let me know how to achieve this using spring-jpa and REST ?
Also it will be great if you please explain how to insert data in this "User_Resource_Privilege" table using REST/curl command ?
What you could do is make an embeddable ID and wrap it with the class. You can afterwards even expand this wrapper class to hold other fields.
java geeks example of embedded id
You would get something like
#Embeddable
public class EmbeddedIdClass implements Serializable {
private String userId;
private String resourceId;
private String privilegeId;
// constructors, getters and setters, equals, etc
}
#Entity
public class Wrapper {
#EmbeddedId
private EmbeddedIdClass id;
// constructors, etc
}
Instead of just using the strings in this example, you should use the complete objects and let hibernate (or something like it) do it's stuff. It should only take the id's into the database and do it's magic itself.
edit:
Just wanting to insert the id's as values, but keeping relationships would look something like this
#Entity
public class Wrapper {
#Id
private String id;
private User user;
private Resource resource;
private Privilege privilege;
// constructors
public Wrapper(final User user, final Resource resource, final Privilege privilege) {
this.user = user;
this.resource = resource;
this.privilege = privilege;
}
}