Why bi-directional ManyToOne causes circular dependency in Hibernate? - java

I have entities in my project(based on Spring-Boot + Hibernate):
#Entity
#Table(name = "user_account")
public class UserAccount {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#NotNull
#Column(name = "username")
private String userName;
#NotNull
#Column(name = "first_name")
private String firstName;
#NotNull
#Column(name = "last_name")
private String lastName;
#NotNull
#Column(name = "password")
private String password;
#CreationTimestamp
#Column(name = "birthday")
private LocalDateTime birthday;
#NotNull
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "role", referencedColumnName = "id")
private UserRole role;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "userAccount", cascade= CascadeType.ALL)
private Set<RentInfo> rents;
}
and
#Entity
#Table(name = "rent_info")
public class RentInfo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#NotNull
#ManyToOne(cascade= CascadeType.ALL)
#JoinColumn(name="user_id")
private UserAccount userAccount;
#CreationTimestamp
#Column(name = "start_date")
private LocalDateTime startDate;
#CreationTimestamp
#Column(name = "end_date")
private LocalDateTime endDate;
#Column(name = "status")
private int status;
}
I want to create bi-directional one-to-many relation, when one user can have several rents and when we can select rents by concrete user_id, but something goes wrong
In response I get this:
{
"id": 1,
"userName": "test#mail.loc",
"firstName": "fName",
"lastName": "lName",
"password": "test",
"birthday": "2001-11-03T14:28:14",
"role": {
"name": "CLIENT"
},
"rents": [
{
"userAccount": {
"id": 1,
"userName": "test#mail.loc",
"firstName": "fName",
"lastName": "lName",
"password": "test",
"birthday": "2001-11-03T14:28:14",
"role": {
"name": "CLIENT"
},
"rents": [
{
"userAccount": {
"id": 1,
"userName": "test#mail.loc",
"firstName": "fName",
"lastName": "lName",
"password": "test",
"birthday": "2001-11-03T14:28:14",
"role": {
"name": "CLIENT"
}
.....
And this is unlimited and logically I have out of memory error. How can I fix this? what I'm doing wrong?

You have two solutions :
Use #JsonIgnore on the #ManyToOne
Do NOT serialize your entities. Use DTOs instead and take care while mapping to avoid circular dependencies

You can also use #JsonManagedReference and #JsonBackReference to solve this infinite recursion problem as follows:
#Entity
#Table(name = "user_account")
public class UserAccount {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#NotNull
#Column(name = "username")
private String userName;
#NotNull
#Column(name = "first_name")
private String firstName;
#NotNull
#Column(name = "last_name")
private String lastName;
#NotNull
#Column(name = "password")
private String password;
#CreationTimestamp
#Column(name = "birthday")
private LocalDateTime birthday;
#NotNull
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "role", referencedColumnName = "id")
private UserRole role;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "userAccount", cascade= CascadeType.ALL)
#JsonManagedReference
private Set<RentInfo> rents;
}
#Entity
#Table(name = "rent_info")
public class RentInfo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#NotNull
#ManyToOne(cascade= CascadeType.ALL)
#JoinColumn(name="user_id")
#JsonBackReference
private UserAccount userAccount;
#CreationTimestamp
#Column(name = "start_date")
private LocalDateTime startDate;
#CreationTimestamp
#Column(name = "end_date")
private LocalDateTime endDate;
#Column(name = "status")
private int status;
}
Nevertheless, I would first map your entities to DTOs. However, you might as well need to use #JsonManagedReference and #JsonBackReference in these new DTO classes if you want all data to be available (one way to avoid this infinite recursion problem would be not mapping userAccount in RentInfoDto but you might not want that because you also want to serialize UserAccountDto data).

Related

Jpa repository filter by enum value

public class EmployeeEntity {
#Id
#Column(name = "id")
#GeneratedValue(strategy= GenerationType.AUTO)
private Long id;
#Length(min = 2, max = 30)
#Column(name = "name")
private String name;
#Length(min = 2, max = 30)
#Column(name = "last_name")
private String lastName;
#Column(name = "email", nullable = false, unique = true)
#Length(max = 50)
private String email;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "employee_id")
private Set<EmployeeRoleEntity> roles;}
This is my Employee class and as you can see inside Employee, I have a set of EmployeeRoleEntity
public class EmployeeRoleEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#NotNull
#Column(name = "role_name")
#Enumerated(EnumType.STRING)
private RoleEntityEnum role;
#ManyToOne
#JoinColumn(name = "employee_id")
#ToString.Exclude
#EqualsAndHashCode.Exclude
private EmployeeEntity employee;
I was trying to filter my employees depends in their role. I created a method like this on my Jpa repository;
List<EmployeeEntity> findByRoles_RoleContainingIgnoreCase( String role);
But it doesn't work and Im so confused to what to do. How can I solve this problem?
Finally I found the answer;
List<EmployeeEntity> findByRoles_Role( RoleEntityEnum role);
This solved my problem. I thought at first the problem was the method but appearently the problem was parameter.

Why #Column(unique = true) does not work?

I have following code:
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "FIRST_NAME")
private String firstName;
#Column(name = "LAST_NAME")
private String lastName;
#NotNull
#Column(name = "LOGIN", unique = true)
private String login;
#Column(name = "PASSWORD")
private String password;
#Column(name = "ROLE")
private UserRole role;
#Column(name = "E_MAIL", unique = true)
private String email;
#Convert(converter = UserStrategyConverter.class)
#Column(name = "STRATEGY")
private UserStrategy userStrategy;
#Column(name = "SUBSCRIPTION")
private Boolean subscription;
#Column(name = "MONEY")
private BigDecimal money;
My problem: When I put this object from postman in json:
{
"firstName": "Daniel",
"lastName": "xxx",
"password": "daniel",
"role": "ROLE_USER",
"email": "test#test.pl",
"subscription": false,
"money": "1200"
}
It create object in entity. Problem is because I can multiply this object again and again instead of unique = true in columns (email and login). Can anyone explain me why?
Hibernate will take into account the constraint unique = true only at the time of schema generation.
During schema generation the following constraint will be added:
alter table User
add constraint UK_u31e1frmjp9mxf8k8tmp990i unique (email)
If you do not use the schema generation there is no sense of using unique = true.

how to send only id instead of object in requestbody?

I have two entities. Customer which is mapped in one to many relation with the CustomerDepartment. CustomerDepartment table has a column to store customer Id.
I want to map them in such a way that Customer Object store a list of Customer Department, and the Customer Department stores the id of the customer it belongs to.
The code that is working compels me to send the all the customer details while creating or updating a customer Department.
Is there a way I can only send the id of the customer and it maps itself?
I have tried changing from -
#JsonBackReference
#ManyToOne
#JoinColumn(name = "customer_no", nullable = false)
private Customer customer;
to this -
#JsonBackReference
#ManyToOne(targetEntity = Customer.class)
#JoinColumn(name = "customer_no", nullable = false)
private Integer customer;
which gives me the requestbody I want but it does not work giving the following error -
2019-08-03 04:59:08 ERROR CustomerController:72 - org.springframework.orm.jpa.JpaSystemException: Error accessing field [private java.lang.Integer com.enquero.pulse.entity.Customer.customerNo] by reflection for persistent property [com.enquero.pulse.entity.Customer#customerNo] : 1; nested exception is org.hibernate.property.access.spi.PropertyAccessException: Error accessing field [private java.lang.Integer com.enquero.pulse.entity.Customer.customerNo] by reflection for persistent property [com.enquero.pulse.entity.Customer#customerNo] : 1
Working Code:
Customer:-
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
#DynamicUpdate
#Entity
#Table(name = "customer")
public class Customer extends Auditable<Integer>{
#Id
#Column(name = "customer_no")
private Integer customerNo;
#NotBlank
#Column(name = "customer_name")
private String customerName;
#Column(name = "industry")
private String industry;
#Column(name = "country")
private String country;
#Column(name = "state")
private String state;
#Column(name = "city")
private String city;
#Column(name = "postal_code")
private String postalCode;
#Column(name = "address_line1")
private String addressLine1;
#Column(name = "address_line2")
private String addressLine2;
#Column(name = "address_line3")
private String addressLine3;
#Column(name = "payment_term")
private String paymentTerm;
#Column(name = "customer_segment")
private String customerSegment;
#JsonFormat(pattern="dd-MMM-yyyy")
#Column(name = "engagement_start_on")
private Date engagementStartOn;
#JsonManagedReference
#OneToMany(fetch = FetchType.LAZY, mappedBy = "customer")
private List<CustomerDepartment> customerDepartments;
}
CustomerDepartment:-
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
#DynamicUpdate
#Entity
#Table(name = "customer_department")
public class CustomerDepartment extends Auditable<Integer>{
#Id
#Column(name = "dept_id", updatable = false, nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer deptId;
#Column(name = "dept_name")
private String deptName;
#Column(name = "primary_contact")
private String primaryContact;
#JsonBackReference
#ManyToOne
#JoinColumn(name = "customer_no", nullable = false)
private Customer customer;
}
Current RequestBody:-
{
"createdBy": 0,
"creationDate": "2019-08-02T23:05:33.993Z",
"customer": {
"addressLine1": "string",
"addressLine2": "string",
"addressLine3": "string",
"city": "string",
"country": "string",
"createdBy": 0,
"creationDate": "2019-08-02T23:05:33.993Z",
"customerDepartments": [
null
],
"customerName": "string",
"customerNo": 0,
"customerSegment": "string",
"engagementStartOn": "string",
"industry": "string",
"lastUpdateDate": "2019-08-02T23:05:33.993Z",
"lastUpdatedBy": 0,
"paymentTerm": "string",
"postalCode": "string",
"state": "string"
},
"deptId": 0,
"deptName": "string",
"lastUpdateDate": "2019-08-02T23:05:33.994Z",
"lastUpdatedBy": 0,
"primaryContact": "string"
}
expected requestbody:-
{
"createdBy": 0,
"creationDate": "2019-08-02T23:05:33.993Z",
"customer": 1, //id instead of json
"deptId": 0,
"deptName": "string",
"lastUpdateDate": "2019-08-02T23:05:33.994Z",
"lastUpdatedBy": 0,
"primaryContact": "string"
}
Have you considered a unidirectional #OneToMany: https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#associations?
For example on CustomerDeparment change
#JsonBackReference
#ManyToOne
#JoinColumn(name = "customer_no", nullable = false)
private Customer customer;
}
to
#JsonBackReference
#ManyToOne
#Column(name = "customer_no")
private int customer;
...and on Customer change
#JsonManagedReference
#OneToMany(fetch = FetchType.LAZY, mappedBy = "customer")
private List<CustomerDepartment> customerDepartments;
}
to
#JsonManagedReference
#OneToMany(cascade = CascadeType.ALL)
private List<CustomerDepartment> customerDepartments;
}
As a bit of an aside, I honestly find Hibernate relationships to sometimes be more a hindrance than a help. As an alternative, you may wish to consider dropping the explicit relationship properties, using "regular" columns (#Column(name="customer_no") private int customer') and just writing queries in your repo classes (ex. findByCustomerNo(int customNumber)) to meet your requirements.

Can't save entity in hibernate

I have created simple CRUD service. With 4 entities: Customer, Provider, Product, Deal.
Customer and Provider entities has composed id AppId with the following structure:
#Getter
#Setter
#Embeddable
#NoArgsConstructor
public class AppId implements Serializable {
private String app;
private String id;
//...
}
Here is business logic I want:
Providers entity cascades and creates Product entities.
When the customer makes deal with provider I need to create entity Deal, which doesn't cascade any other entities.
It just has fields which refer to provider, customer and product of the deal.
I created some providers and customers.
Then I tried to create deal, but I got fields customer and provider null.
Here are my entities definitions:
Provider:
#Entity
#Getter
#Setter
#ToString
#NoArgsConstructor
#Table(name = "provider")
public class Provider implements Serializable {
#EmbeddedId
#Column(name = "appid")
private AppId appId;
#Column(name = "name")
private String name;
#Column(name = "firstname")
private String firstName;
#Column(name = "lastname")
private String lastName;
#Column(name = "latitude")
private float latitude;
#Column(name = "longitude")
private float longitude;
#Column(name = "work_date")
private Date workDate;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "provider_product"
, joinColumns = {
#JoinColumn(name = "provider_app"),
#JoinColumn(name = "provider_id")
}
, inverseJoinColumns = #JoinColumn(name="product_id"))
private Set<Product> products;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumns({
#JoinColumn(name = "app", referencedColumnName = "app", updatable = false, insertable = false),
#JoinColumn(name = "id", referencedColumnName = "id", updatable = false, insertable = false)
})
private List<Deal> dealList = new ArrayList<>();
}
Customer:
#Entity
#Getter
#Setter
#ToString
#NoArgsConstructor
#Table(name = "customer")
public class Customer implements Serializable {
#EmbeddedId
#Column(name = "appid")
private AppId appId;
#Column(name = "firstname")
private String firstName;
#Column(name = "lastname")
private String lastName;
public Customer(AppId appId, String firstName, String lastName) {
this.appId = appId;
this.firstName = firstName;
this.lastName = lastName;
}
}
Product:
#Entity
#Getter
#Setter
#ToString
#NoArgsConstructor
#Table(name = "product")
public class Product implements Serializable {
#Id
#GeneratedValue
private long id;
#Column(name = "name")
private String name;
#Column(name = "cost")
private long cost;
}
Deal:
#Entity
#Getter
#Setter
#ToString
#NoArgsConstructor
#Table(name = "deal")
public class Deal implements Serializable {
#Id
#GeneratedValue
private long id;
#ManyToOne
#JoinColumns({
#JoinColumn(name = "provider_app", referencedColumnName = "app", insertable = false, updatable = false),
#JoinColumn(name = "provider_id", referencedColumnName = "id", insertable = false, updatable = false)
})
private Provider provider;
#ManyToOne
#JoinColumns({
#JoinColumn(name = "customer_app", insertable = false, updatable = false),
#JoinColumn(name = "customer_id", insertable = false, updatable = false)
})
private Customer customer;
#ManyToMany
#JoinTable(name = "deal_product"
, joinColumns = #JoinColumn(name="deal_id", insertable = false, updatable = false)
, inverseJoinColumns = #JoinColumn(name="product_id", insertable = false, updatable = false))
private Set<Product> product;
// deal is complete when provider entered deal id
#Column(name = "closed")
private boolean closed = false;
}
By removing insertable = false for customer and provider fields in the Deal entity, everything works fine.
{
"id": 5,
"provider": {
"appId": {
"app": "vk",
"id": "123"
},
"name": null,
"firstName": null,
"lastName": null,
"latitude": 0,
"longitude": 0,
"workDate": null,
"products": null,
"dealList": []
},
"customer": {
"appId": {
"app": "vk",
"id": "123"
},
"firstName": null,
"lastName": null
},
"product": [
{
"id": 2,
"name": "Temp",
"cost": 100
}
],
"closed": false
}
I could get the following response.
insertable = false on a field means when you are saving the entity you won't be saving the value for that field and will set the field explicitly somewhere.
insertable = true doesn't mean you will create a new Customer or Provider, that is handled by CascadeType

Getting Other Entity List

I am using sprint jpa data to get the list of student and their corresponding courses.
#Entity
#Table(name = "student")
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "student_id")
private Integer studentId;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 100)
#Column(name = "fname")
private String fname;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 100)
#Column(name = "lname")
private String lname;
// #Pattern(regexp="[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*#(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?", message="Invalid email")//if the field contains email address consider using this annotation to enforce field validation
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 100)
#Column(name = "email")
private String email;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 100)
#Column(name = "username")
private String username;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 255)
#Column(name = "password")
private String password;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "student")
#JsonManagedReference
private Set<StudentCourse> studentCourseSet;
Course entity
#Entity
#Table(name = "course")
public class Course implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "course_id")
private Integer courseId;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 20)
#Column(name = "couse_code")
private String couseCode;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 100)
#Column(name = "course_name")
private String courseName;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "course")
#JsonManagedReference
private Set<StudentCourse> studentCourseSet;
Student Course
#Entity
#Table(name = "student_course")
public class StudentCourse implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
protected StudentCoursePK studentCoursePK;
#JoinColumn(name = "course_id", referencedColumnName = "course_id", insertable = false, updatable = false)
#ManyToOne(optional = false)
#JsonBackReference
private Course course;
#JoinColumn(name = "student_id", referencedColumnName = "student_id", insertable = false, updatable = false)
#ManyToOne(optional = false)
#JsonBackReference
private Student student;
And
#Embeddable
public class StudentCoursePK implements Serializable {
#Basic(optional = false)
#NotNull
#Column(name = "student_id")
private int studentId;
#Basic(optional = false)
#NotNull
#Column(name = "course_id")
private int courseId;
I have this RestController that return list of student.
#RestController
public class StudentController {
#Autowired
private StudentService studentService;
#Autowired
private CourseService courseService;
#Autowired
StudentRepositoryImpl studentRepositoryCustomImpl;
#RequestMapping(value = "/studentlist")
public Iterable<Student> getStudentList() {
return studentService.getStudentList();
}
The output looks as follow
// 20170929084542
// http://localhost:8080/studentlist
[
{
"studentId": 1,
"fname": "abc",
"lname": "efg",
"email": "a#b.com",
"username": "as",
"password": "as",
"studentCourseSet": [
{
"studentCoursePK": {
"studentId": 1,
"courseId": 4
}
},
{
"studentCoursePK": {
"studentId": 1,
"courseId": 1
}
},
{
"studentCoursePK": {
"studentId": 1,
"courseId": 2
}
},
{
"studentCoursePK": {
"studentId": 1,
"courseId": 3
}
}
]
}
]
How can I include student course name list for the student on the output. I am using spring boot

Categories