I have two entities. I want a bidirectional relationship between projects and tasks.
A project has one or more tasks
A task is associated to only one project
There are my entities:
ProjectEntity.java
#Entity
#Table(name = "projects")
public class ProjectEntity {
#Id
#GeneratedValue
#Column(name = "pr_id")
private long id;
#Column(name = "pr_name", nullable = false)
private String name;
#OneToMany(cascade=CascadeType.ALL, mappedBy="project",orphanRemoval=true)
#JsonBackReference
private Set<TaskEntity> tasks = new HashSet<TaskEntity>();
// Getters, constructors, setters
TaskEntity.java
#Entity
#Table(name = "tasks")
public class TaskEntity {
#Id
#GeneratedValue
#Column(name = "ta_id")
private long id;
#Column(name = "ta_name")
private String name;
#ManyToOne
#JoinColumn(name="pr_id")
#JsonManagedReference
private ProjectEntity project;
// Getters, constructors, setters
I would like to have the list of tasks in each ProjectEntity, and in each TaskEntity the project associated.
Here, I'm using #JsonManagedReference and #JsonBackReference to stop the infinite recursion it generated, but in my ProjectEntity, I don't have the tasks list (because of the #JsonBackReference) ...
Could you help me to get back tasks list in ProjectEntity ? I heard about #JsonIdentifyInfo, but I have not managed to do with.
Hope I'm understandable :)
The solution that I know is really using '#JsonIdentityInfo' in instead of '#JsonBackReference' and '#JsonManagedReference'.
You have to remove the '#JsonBackReference' and '#JsonManagedReference' to use '#JsonIdentityInfo'.
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property="id")
#Entity
#Table(name = "projects")
public class ProjectEntity {
#Id
#GeneratedValue
#Column(name = "pr_id")
private long id;
#Column(name = "pr_name", nullable = false)
private String name;
#OneToMany(cascade=CascadeType.ALL, mappedBy="project",orphanRemoval=true)
private Set<TaskEntity> tasks = new HashSet<TaskEntity>();
}
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property="id")
#Entity
#Table(name = "tasks")
public class TaskEntity {
#Id
#GeneratedValue
#Column(name = "ta_id")
private long id;
#Column(name = "ta_name")
private String name;
#ManyToOne
#JoinColumn(name="pr_id")
private ProjectEntity project;
}
You could opt for implement a custom serializer for the list ProjectEntity.tasks. But you must controll/stop the serialization cycle at some point; this will depend on your requirements.
Here is an example of what I mean.
#Entity
#Table(name = "projects")
public class ProjectEntity {
...
#OneToMany(cascade=CascadeType.ALL, mappedBy="project", orphanRemoval=true)
#JsonSerialize(using = CustomListSerializer.class)
private Set<TaskEntity> tasks = new HashSet<TaskEntity>();
}
And the CustomListSerializer could be something like this,
public class CustomListSerializer extends JsonSerializer<List<TaskEntity>>{
#Override
public void serialize(List<TaskEntity> tasks, JsonGenerator generator, SerializerProvider provider)
throws IOException, JsonProcessingException {
generator.writeStartArray();
for (TaskEntity task : tasks) {
generator.writeStartObject("taskEntity")
.write("id", task.id)
.write("name", task.name)
.write("project", task.project.id) //<- here you stop the cycle
.writeEnd();
}
generator.writeEnd();
}
}
Note that it is basically the example of the mentioned mini guide but it serialize information of the elements of project's tasks list instead of the tasks' id only.
Related
In my Spring Boot app, I use Hibernate and applied the necessary relations to the following entities properly.
#Entity
public class Recipe {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable=false, length=50)
private String title;
#OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL)
private List<RecipeIngredient> recipeIngredients = new ArrayList<>();
}
#Entity
public class RecipeIngredient {
#EmbeddedId
private RecipeIngredientId recipeIngredientId = new RecipeIngredientId();
#ManyToOne(optional = true, fetch = FetchType.LAZY)
#MapsId("recipeId")
#JoinColumn(name = "recipe_id", referencedColumnName = "id")
private Recipe recipe;
#ManyToOne(optional = true, fetch = FetchType.LAZY)
#MapsId("ingredientId")
#JoinColumn(name = "ingredient_id", referencedColumnName = "id")
private Ingredient ingredient;
}
#Entity
public class Ingredient
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique=true, nullable=false, length=50)
#EqualsAndHashCode.Include
private String name;
#OneToMany(mappedBy = "ingredient", cascade = CascadeType.ALL)
private Set<RecipeIngredient> recipeIngredients = new HashSet<>();
}
Now I am trying to retrieve data by merging related entities. For example, when retrieving a Recipe, I also need to retrieve all Ingredients belonging to this Recipe.
As far as I know, I can use Projection and maybe it is better to only use Hibernate features and retrieve related table data via Java Stream. I have no idea how should I retrieve data via Hibernate.
Suppose that I just need an Optional<Recipe> that has List<Ingredient>. Then, I probably need a DTO class something like that:
#Data
public class ResponseDTO {
private Long id;
private String title;
List<RecipeIngredient> ingredients;
// getter, setter, constructor
}
So, how should I populate this DTO with the requested Recipe and corresponding Ingredient data (getting Ingredient names besides id values) using Java Stream?
Or if you suggest Projection way, I tried it but the data is multiplied by the ingredient count belonging to the searched recipe.
Update:
#Getter
#Setter
#NoArgsConstructor
public class ResponseDTO {
private Long id;
private String title;
List<IngredientDTO> ingredientDTOList;
public ResponseDTO(Recipe recipe) {
this.id = recipe.getId();
this.title = recipe.getTitle();
this.ingredientDTOList = recipe.getRecipeIngredients().stream()
.map(ri -> new IngredientDTO(ri.getIngredient().getName()))
.toList();
}
}
#Getter
#Setter
public class IngredientDTO {
private Long id;
private String name;
public IngredientDTO(String name) {
this.name = name;
}
}
First, in the ResponseDTO you will need you change the type of ingredients from List<RecipeIngredient> to List<Ingredient>.
To manually perform the mapping, you should use (to map from a suppose Recipe recipe to a RespondeDTO response):
ResponseDTO recipeToResponseDTO(Recipe recipe) {
ResponseDTO response = new ResponseDTO();
response.setId(recipe.getId());
response.setTitle(recipe.getTitle());
response.setIngredients(recipe.recipeIngredients.stream()
.map(RecipeIngredient::getIngredient()
.collect(Collectors.toList());
return response;
}
On the other hand, to model a n-n relation, I encourage you to use the approach proposed by E-Riz in the comment.
I'm trying to build a little project-management tool as my first Spring Boot / JPA / H2 / REST Application using lombok annotations for avoiding boilerplate-code. I followed several promising tutorials. But I'm failing at the very end, when I try to intantiate some dummy data to test the database and start the service.
Till now it had two tables: "T_PROJECT" & "T_EMPLOYEE"
But I also want to be able to visualize, in which period an employee works for a specific project. So I need a third table "T_EMPLOYEE_ACTIVITY" with two extra columns: "START_DATE" & END_DATE".
I made an
ER-Diagram that should help to understand how these tables must work together.
I found already this one here:
JPA 2.0 many-to-many with extra column
... and tried to build it the same way:
The Project entity:
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "T_PROJECT")
public class Project implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "PROJECT_ID")
private Long id;
private String name;
#Column(name = "START_DATE")
private String startDate;
#Column(name = "END_DATE")
private String endDate;
private Status status;
#OneToMany(mappedBy = "project")
#Column(name = "EMPLOYEE_ACTIVITIES")
private Set<EmployeeActivity> employeeActivities;
}
The Employee entity:
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "T_EMPLOYEE")
public class Employee implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "EMPLOYEE_ID")
private Long id;
#Column(name = "FIRST_NAME")
private String firstName;
#Column(name = "LAST_NAME")
private String lastName;
private String role;
#Column(name = "HOURS_PER_WEEK")
private BigDecimal hoursPerWeek;
#OneToMany(mappedBy = "employee")
#Column(name = "EMPLOYEE_ACTIVITIES")
private Set<EmployeeActivity> employeeActivities;
}
EmployeeActivity entity:
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "T_EMPLOYEE_ACTIVITY")
public class EmployeeActivity implements Serializable {
#Id
#Column(name = "EMPLOYEE_ACTIVITY_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "PROJECT_ID")
private Project project;
#ManyToOne
#JoinColumn(name = "EMPLOYEE_ID")
private Employee employee;
#Column(name = "START_DATE")
private String startDate;
#Column(name = "END_DATE")
private String endDate;
}
In the Application.java (with main & run method), I tried to intantiate it like this and failed:
#Override
public void run(String... args) throws Exception {
Project project = new Project(null, "BERLIN_AIRPORT", "2006-05-01", "2020-10-31", Status.COMPLETED, Set.of( ??? ));
projectRepository.save(project);
Employee employee = new Employee(null, "Jim", "Beam", "Architect", BigDecimal.valueOf(40d), Set.of( ??? ));
employeeRepository.save(employee);
EmployeeActivity employeeActivity = new EmployeeActivity();
employeeActivity.setProject(project);
employeeActivity.setEmployee(employee);
employeeActivity.setStartDate("2006.05.01");
employeeActivity.setEndDate("2010.12.12");
employeeActivityRepository.save(employeeActivity);
}
So both - Project and Employee - have an attribute "employeeActivities", that needs some value, when I make a new Object.
But at this point, there is no EmployeeActivity-Object that i could use.
How do I manage this?
Thanks a lot & have nice day!
NicerDicer
I meanwhile found the solution. The problem was, that I tried to use the AllArgsConstructor that has been generated via lombok.
The AllArgsConstructor expects of course all attributes that I declared in the entitties.
The solution is to use setters (in my case auto-generated by the lombok #Data annotation) and to not set the id and employeeActivities from project & employee.
(Alternatively you can of course write your own constructor.)
#Override
public void run(String... args) throws Exception {
Project project_1 = new Project();
project_1.setName("BERLIN_AIRPORT");
project_1.setStartDate("2006-05-01");
project_1.setEndDate("2020-10-31");
project_1.setStatus(Status.COMPLETED);
projectRepository.save(project_1);
Employee employee_1 = new Employee();
employee_1.setFirstName("Jim");
employee_1.setLastName("Beam");
employee_1.setRole("Architect");
employee_1.setHoursPerWeek(BigDecimal.valueOf(40d));
employeeRepository.save(employee_1);
EmployeeActivity employeeActivity = new EmployeeActivity();
employeeActivity.setProject(project_1);
employeeActivity.setEmployee(employee_1);
employeeActivity.setStartDate("2019-05-01");
employeeActivity.setEndDate("2022-12-12");
employeeActivityRepository.save(employeeActivity);
}
I'm trying to build build service, which saves object with sub-objects, but getting error. In result object data fields saved, but sub-object not.
I have the next object. The main is Order and sub-object is Partner:
#Getter
#Setter
#Entity
#Table(name = "orders")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "order_id")
private int orderId;
#OneToMany(mappedBy = "order", fetch = FetchType.EAGER,
cascade = CascadeType.ALL)
private Set<Partner> partners;
}
#Getter
#Setter
#Entity
#Table(name = "partners")
public class Partner implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "partner_id")
private int id;
#ManyToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "order_id", nullable = false)
private Order order;
}
I use standard embedded method "save" from Spring Jpa Repository:
#Repository
public interface OrdersRepository extends JpaRepository<Order, Integer> {
}
and service, which call this Repository:
#Service
public class OrdersServiceImpl implements OrdersService {
#Autowired
private OrdersRepository repository;
#Override
public Order save(Order order) {
return repository.save(order);
}
}
Does someone have an idea why Partners are not saved?
Thanks a lot!
Because the relationship owner is Partner, so that you need to save the Order first. Or you can put cascade = CascadeType.PERSIST on private Order order;
I have a table master table user ,topics table and comments table
where in for a single topic there can be multiple comments
user table will be already populated.
I will get a post request to save the topic with structure like below
{
"topicId":"T001",
"title":"stackoverflow",
"commentBeans":[
{
"comment":"developer platform"
},
{
"comment":"developer communtiy"
}
]
}
Frameworks used:
spring boot
JPA
DB : postgressql
I am able to save the data the traditional way (i.e get the request and save topic bean first. get the primarykey from saved entity and loop the list of commentbean where user num will be set dynamically by another get service and save them)
I wanted to know if there is anyway to save the data with single save query.
#Entity
#Table(name ="user")
public class User implements Serializable {
#Id
#Column(name = "user_num")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long userNum;
#Column(name = "user_id")
private String userId;
#Column(name = "username")
private String userName;
}
#Entity
#Table(name = "topics")
public class TopicBean implements Serializable {
#Id
#Column(name = "topic_num")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long topicNum;
#Column(name = "topicId")
private String topicId;
#Column(name = "title")
private String title;
#OneToMany(mappedBy="topicBean")
private List<CommentBean> commentBeans;
}
#Entity
#Table(name = "comments")
public class CommentBean implements Serializable {
#EmbeddedId
private CommentBeanKey key;
#Column(name = "comment")
private string comment;
#ManyToOne
#JoinColumn(name="topic_num")
private TopicBean topicBean;
#ManyToOne
#JoinColumn(name="user_num")
private TopicBean topicBean;
}
#Embeddable
public class CommentBeanKey implements Serializable{
private static final long serialVersionUID = 5889249943605061539L;
#Column(name ="topic_num")
private Long topicNum;
#Column(name ="user_num")
private Long userNum;
}
I saw the below link and am little worried if am doing the wrong way. any help is appreciated.
https://thoughts-on-java.org/hibernate-tips-how-to-map-an-entity-to-multiple-tables/
Parent.java
#Entity
#Table(name = "parent")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#ToString
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int parentId;
private String name;
#OneToMany(mappedBy="parent",fetch=FetchType.LAZY,cascade = CascadeType.PERSIST)
private List<Child> child = new ArrayList<Child>();
}
Child.java
#Entity
#Table(name = "child")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#ToString
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int childId;
private String account;
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Parent.class)
#JoinColumn(name="parentId", referencedColumnName = "parentId", nullable = false)
private Parent parent;
}
Controller.java
//save Child with Parent at same
#PostMapping(value = "/onetomany")
public String OneToMany(#RequestBody Parent parent)
{
System.out.println("Parent: "+parent.toString());
for (Child child : parent.getChild()) {
child.setParent(parent);
}
parent.setChild(parent.getChild());
parentRepository.save(parent);
return "saved";
/*{
"name":"Romil",
"child":[
{"account":"1"},
{"account":"2"}
]
}*/
}
//save Child with Parent's ID
#PostMapping(value = "/onetomanyPID")
public String OneToMany(#RequestBody Child child)
{
child.setParent(child.getParent());
childRepository.save(child);
return "saved";
/*{
"account":"3",
"parent":{
"parentId":"1",
"name":"Romil"
}
}*/
}
I am trying some hibernate.The following is the pojo I am using,
#Entity
#Table(name = "person")
public class Person {
#Id
#GeneratedValue
#Column(name = "person_id")
private long person_id;
#Column(name = "name")
private String name;
#Column(name = "Address")
private String Address;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "person" )
private Set<Phone> phone;
//Getters ande Setters
}
#Entity
#Table(name = "phone")
public class Phone{
#Id
#GeneratedValue
#Column(name = "phone_id")
private long phone_id;
#Column(name = "name")
private String name;
#ManyToOne(cascade = CascadeType.MERGE,fetch = FetchType.EAGER)
#JoinColumn(name = "person_id")
private Person person ;
//Getters ande Setters
}
What I want is when I fetch a record from person and need corresponding all phone details. (Like Select * from person) I have around 1360 data in person and nearly double in phone. But for some reason error is thrown. I am not able to see full error stack . Below is the error I am getting.
at
com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:505)
~[jackson-databind-2.4.6.jar:2.4.6] at
com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:639)
~[jackson-databind-2.4.6.jar:2.4.6] at
com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:152)
~[jackson-databind-2.4.6.jar:2.4.6] at
com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:505)
~[jackson-databind-2.4.6.jar:2.4.6] at
com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:639)
~[jackson-databind-2.4.6.jar:2.4.6] at
com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:152)
~[jackson-databind-2.4.6.jar:2.4.6] at
com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serializeContents(CollectionSerializer.java:117)
~[jackson-databind-2.4.6.jar:2.4.6] at
com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serializeContents(CollectionSerializer.java:23)
~[jackson-databind-2.4.6.jar:2.4.6] at
.....
I was not able to post all error that I got
Using JsonManagedReference and JsonBackReference annotations may solve your problem.
While jackson trying to convert objects to json, visits objects and
their attributes. So if objects have bi-directional relations, for
jackson we need to think about cyclic dependencies. Jackson starts
serialize person and see the phone list and take a phone from list and
start serialize phone and sees person in phone and take person from
phone and start serilize it bla bla bla so this is an endless loop. If
jackson sees these annotations, stops and breaks the loop.
Give it a try as below code;
#Entity
#Table(name = "person")
public class Person {
#Id
#GeneratedValue
#Column(name = "person_id")
private long person_id;
#Column(name = "name")
private String name;
#Column(name = "Address")
private String Address;
#JsonManagedReference
#OneToMany(fetch = FetchType.EAGER, mappedBy = "person" )
private Set<Phone> phone;
// Getters and Setters
}
#Entity
#Table(name = "phone")
public class Phone{
#Id
#GeneratedValue
#Column(name = "phone_id")
private long phone_id;
#Column(name = "name")
private String name;
#ManyToOne(cascade = CascadeType.MERGE,fetch = FetchType.EAGER)
#JoinColumn(name = "person_id")
#JsonBackReference
private Person person;
// Getters and Setters
}
You can alternatively use #JsonIdentityInfo on classes
#Entity
#JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="#id")
#Table(name = "phone")
public class Phone {
}
#Entity
#JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="#id")
#Table(name = "person")
public class Person {
}
I was also getting the same error.
Using #JsonBackReference and #JsonManagedReference was still giving me error so I used #JsonIdentityInfo and it worked like a charm.
Below are my classes :-
BookModel :
#Data
#Entity
#Table(name = "book")
#JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="bookId")
public class BookModel implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "book_id")
private int bookId;
#Column(name="book_name")
private String bookName;
#Column(name="book_author")
private String bookAuthor;
#Column(name="book_publish_date")
private Date bookPublishDate;
#Column(name="book_price")
private double bookPrice;
#OneToMany(mappedBy = "book_model")
List<BookImagesModel> bookImagesModels;
//getters and setters
//default constructor
}
BookImagesModel :
#Data
#Entity
#Table(name = "book_images")
#JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="imageId")
public class BookImagesModel implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "image_id")
private long imageId;
#ManyToOne
#JoinColumn(name = "book_id")
private BookModel book_model;
#Column(name = "image_path")
private String imagePath;
//getters and setters
//default constructor
}
I used Mysql8 database with spring boot.