Hibernate: One to many mapping is leading to circular objects - java

I have an use case where a single user can have multiple houses. This is how the models look like
ApplicationUser.java
#Getter
#Setter
#Entity
public class ApplicationUser {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
#Column(nullable = false, unique = true)
private String username;
#Column(nullable = false)
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String password;
#Column(nullable = false)
private String emailId;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "applicationUser", cascade = CascadeType.REMOVE)
private List<House> houses;
}
House.Java
#Getter
#Setter
#Entity
public class House {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
#ManyToOne()
#JoinColumn(name = "application_user")
private ApplicationUser applicationUser;
#Column(name = "House_Name")
private String houseName;
}
HouseRepository
public interface HouseRepository extends JpaRepository<House, Long> {
public House findByHouseName(String houseName);
public List<House> findAllByApplicationUser_Username(String userName);
}
Whenever I try to retrieve any house, the house object contains the user object and the user object again contains the house object. This goes on infinitely.
{
"id": 3,
"applicationUser": {
"id": 2,
"username": "test",
"emailId": "testmail",
"houses": [
{
"id": 3,
"applicationUser": {
"id": 2,
"username": "test",
"emailId": "testmail",
"houses": [
{
"id": 3,
"applicationUser": {
"id": 2,
"username": "test",
"emailId": "testmail",
"houses": [
How do I stop that from happening?

Since your House has an ApplicationUser and your ApplicationUser has a list of Houses, you've defined the classes to be circular.
Odds are that your Object Oriented model is 100% identical to the database model. This is probably a bad idea; as in a model of the home application, you wouldn't generally hold the application within the user embedded within the application.
Read What is “the inverse side of the association” in a bidirectional JPA OneToMany/ManyToOne association? for more details.

Related

Many to many json request

A Contract has several AbsenceType, and an AbsentType can be in several different Contracts. So I made a manyToMany relation in both classes.
Entity
#Table(name = "contract")
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
#EntityListeners(AuditingEntityListener.class)
public class Contract {
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "name")
private String name;
#Column(name = "employee")
private int employee;
#ManyToMany(mappedBy = "contracts")
private List<AbsenceType> absence_types;
// ... getteur setteur contructor
#Entity
#Table(name = "absence_type")
#EntityListeners(AuditingEntityListener.class)
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class AbsenceType {
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "name")
private String name;
// hexa : #FFFFFF
#Column(name = "color")
private String color;
#Column(name = "is_paid")
private boolean is_paid;
#ManyToMany
#Column(name = "contracts")
private List<Contract> contracts;
// ... getteur setteur contructor
I want to be able to create empty Absence types and then when I create a contract, give in my json, the previously created absenceType and not create a new AbsenceType.
In the idea I do this:
{
"id": 1 # Exemple, in real in don't had id on json
"name": "Congé sans solde",
"color": "ff4040",
"is_paid": false,
"contracts": []
}
and after ->
{
"name": "Contract4",
"employee": 3,
"absence_types" : [
{
"id":1
}
]
}
But the response when i get all contracts is:
{
"id": 621,
"name": "Contract4",
"employee": 3,
"absenceTypes": []
}
But i want :
{
"id": 621,
"name": "Contract4",
"employee": 3,
"absenceTypes": [
{
"id": 1,
"name": "Congé sans solde",
"color": "ff4040",
"contracts" : [ dudo, i don't think about infinite recursion problem for the moment haha]
}
}
For all of my DAO i have a generic class, how look like this
public void save(T obj) {
Session session = openSession();
Transaction transaction = session.beginTransaction();
session.saveOrUpdate(obj);
transaction.commit();
session.close();
}
and on my Contract controller
#PostMapping("")
public ResponseEntity<String> create(#RequestBody Contract contract) {
// Error 422 if the input role variable is null
if (contract == null)
new ResponseEntity<String>("Can't create null absenceType", HttpStatus.UNPROCESSABLE_ENTITY);
contractDAO.save(contract);
return new ResponseEntity<>("absenceType saved !", HttpStatus.OK);
}

Spring Boot JPA Many to many relationship. Getting unexpected response

I have two entity User and Group and the relationship between both entities are many-to-many. When I call view-group/groupName, I am getting the list of users of group as expected. But when I call view-user/userEmail, I am not getting the list of groups with user details of which user is part of.
Group.java
#Entity
#Table(name = "group_")
public class Group {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
private String groupName;
#ManyToMany
#JoinTable(name = "group_user",
joinColumns = { #JoinColumn(name = "group_id") },
inverseJoinColumns = {#JoinColumn(name = "user_id") })
public Set<User> usersOfgroup = new HashSet<>();
public Group() {
}
}
User.java
#Entity
#Table(name="users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
#Column(name="name")
private String name;
#NotBlank
#Column(name="email")
private String email;
#JsonIgnore
#ManyToMany(mappedBy = "usersOfgroup")
public Set<Group> memberInGroups =new HashSet<>();
public User() {
}
localhost:8080/view-group/groupName
{
"id": 1,
"groupName": "Group1",
"usersOfgroup": [
{
"id": 2,
"name": "Abhishek",
"email": "Abhishek#abc.com",
}
]
}
localhost:8080/view-user/Abhishek#abc.com
{
"id": 2,
"name": "Abhishek",
"email": "Abhishek#abc.com",
}
Expected response :
{
"id": 2,
"name": "Abhishek",
"email": "Abhishek#abc.com",
"memberInGroups":[
{
"id": 1,
"groupName": "Group1",
}
]
}
You have added #JsonIgnore on public Set<Group> memberInGroups =new HashSet<>();, thats why the json response doesn't have the data for this. Remove the annotation and you will see the expected response
The #JsonIgnore annotation is used to ignore the logical property used in serialization and deserialization.
You can go with below. It will address your immediate need for this specific case. Usually for a many-to-many, bi-directional relationship with lists, usually the solution is to decide which side of your relation is dominant and to have a JsonManagedReference and JsonBackReference combo, or use #JsonIdentityInfo if you have a list. Below is very good read on bidirectional cases to avoid infinite loops..
https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion
Coming to the solution I am referring to, you will have to override the getter of the list attribute and also use a #JsonInclude
In your Group class - use #JsonIgnore as shown and also put the getter as below to manually kill the loop
#ManyToMany
#JoinTable(name = "group_user", joinColumns = { #JoinColumn(name = "group_id") }, inverseJoinColumns = {
#JoinColumn(name = "user_id") })
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public Set<User> usersOfgroup = new HashSet<>();
public Set<User> getUsersOfgroup() {
return this.usersOfgroup.stream().map(user -> {
user.memberInGroups = new HashSet<>();
return user;
}).collect(Collectors.toSet());
}
and in your User class, too do the same.
#ManyToMany(mappedBy = "usersOfgroup")
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public Set<Group> memberInGroups = new HashSet<>();
public Set<Group> getMemberInGroups() {
return this.memberInGroups.stream().map(group -> {
group.usersOfgroup = new HashSet<>();
return group;
}).collect(Collectors.toSet());
}

Spring Boot Can't get the relation entity with findAll or findByColumnName method

I'm just trying to test the relation #ManyTonOne in Spring Boot (Spring Data JPA)n so I've created two simple Class Book and Author
Here is the Class Book and Author :
#Entity
#Table(name = "book")
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "title")
private String title;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "author_id", nullable = false)
#JsonIgnore
//#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
private Author author;
Class Author:
#Entity
#Table(name = "author")
public class Author {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "fullname")
private String fullame;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "author", fetch = FetchType.LAZY)
private Set<Book> books;
I just want when i try to call findAll() for books i get author also, when i make it by default i get this result without the author :
"_embedded": {
"books": [
{
"title": "Book1",
"_links": {
"self": {
"href": "http://localhost:8080/api/books/1"
},
"book": {
"href": "http://localhost:8080/api/books/1"
}
}
},
Or when i write directly method findAll in controller :
#RestController
public class BookRestController {
#Autowired
BookRepository bookRepo;
#RequestMapping("/books1/")
public List<Book> createInvoice() {
List<Book> list = bookRepo.findAll();
System.out.println(list);
return list;
}
i get this result :
[
{
"id": 1,
"title": "Book1"
},
{
"id": 2,
"title": "Book2"
},
I've tried also to search by title findByTitle(string), I don't get the author also
A different example that I found is about the second relation #OneToMany, not the other way
What I must add in my Entity or repository or controller to retrieve (with a good way) the author id?
I think without the JsonIgnore maybe you're just going right into recursion hell, since the book has an author and the author has minimum this book, and the book has the author...
Try a getter on the AuthorId, something like
public Long getAuthorId() {
return (author == null) ? null : author.getId());
}
EDIT:
wrote the maybe before your comment, now I'm quite sure :-)
Remove #JsonIgnore and use #JsonIdentityInfo on both classes to get author also for every book.
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,property = "id")
public class Book {
...
}
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,property = "id")
public class Author {
...
}

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.

Foreign key doesn't work on the result of POST but GET works

I have a problem working with foreign keys in Spring. The returned object doesn't have the correct value on my relation ship.
The orderTypeId of an Order is missing when in the CREATED response but a subsequent GET request yields the expected result.
Expected behavior
Here's a small example of what I want to achieve.
Request
POST /api/orders
{
"orderType": "orderTypes/1",
}
or GET /api/orders/2
Output
{
"id": 2,
"orderTypeId": 1, // either this
"orderType": "orderTypes/1" // or this
"_links": {
"self": {
"href": "http://localhost:8090/api/orders/2"
},
"order": {
"href": "http://localhost:8090/api/orders/2"
},
"orderType": {
"href": "http://localhost:8090/api/orders/2/orderType"
},
"orderType": {
"href": "orderTypes/1" // this could even be acceptable
}
}
}
My code
Entites
#Data
#Entity
public class Order {
#Id
#GeneratedValue
private Long id;
#Column(name = "order_type_id", nullable = false, insertable = false, updatable = false)
private Long orderTypeId;
#NotNull
#ManyToOne
#JoinColumn(name = "order_type_id")
private OrderType orderType;
}
#Data
#Entity
public class OrderType {
#Id
#GeneratedValue
private Long id;
#Column(nullable = false)
private String name;
#JsonIgnore
#OneToMany(mappedBy = "orderType")
private List<Order> orders;
}
Controller
#RepositoryRestResource
public interface OrderRepository extends JpaRepository<Order, Long> {}
POST
POST /api/orders
{
"orderType": "orderTypes/1",
}
Output
{
"id": 2,
"orderTypeId": null,
"_links": {
"self": {
"href": "http://localhost:8090/api/orders/2"
},
"order": {
"href": "http://localhost:8090/api/orders/2"
},
"orderType": {
"href": "http://localhost:8090/api/orders/2/orderType"
}
}
}
However if I do GET /orders/2 orderTypeId is correctly set.
What am I doing wrong?
UPDATE
I tried something else
#Data
#Entity
public class Order {
#Id
#GeneratedValue
private Long id;
#NotNull
#ManyToOne
#JoinColumn
#JsonManagedReference
#RestResource(exported=false) // ADDED
private OrderType orderType;
}
#Data
#Entity
public class OrderType {
#Id
#GeneratedValue
private Long id;
#Column(nullable = false)
private String name;
// removed orders
}
GET /api/orders/2
{
"id": 2,
"orderTypeId": {
"id": 1,
"name": "foo"
},
"_links": {
"self": {
"href": "http://localhost:8090/api/orders/2"
},
"order": {
"href": "http://localhost:8090/api/orders/2"
}
}
}
But now POST doesn't work :(
POST /api/orders
{
"orderType": "orderTypes/1",
}
Returns 400 Bad Request...
Using #JsonIgnore is not the recommended way, to manage cyclic dependency please use #JsonManagedReference in your entities.
Entities:
#Data
#Entity
public class Order {
#Id
#GeneratedValue
private Long id;
#Column(name = "order_type_id", nullable = false, insertable = false, updatable = false)
private Long orderTypeId;
#JsonBackReference(value="name")
#ManyToOne
#JoinColumn(name = "order_type_id")
private OrderType orderType;
}
#Data
#Entity
public class OrderType {
#Id
#GeneratedValue
private Long id;
#Column(nullable = false)
private String name;
#JsonManagedReference(value = "name")
#OneToMany(mappedBy = "orderType")
private List<Order> orders;
}
Now to answer your question, the return of post API will not return OrderType as Hibernate does not recognise it as an initialised entity.
You can either do a GET call later as happening in your case already or have a rest controller in a POST api after saving you can do a get call by id before returning or use a DTO for sending response.
The problem is that this field will not be populated when a new instance is constructed i.e. as a result of a POST request. It will however be populated in response to a GET request i.e. it will be populated by Hibernate as a result of the JPA annotations on the field.
I am not quite sure why you would need to map this as a persistent field. Simply adding a getter should mean it is included in any JSON response:
#Data
#Entity
public class Order {
#Id
#GeneratedValue
private Long id;
#NotNull
#ManyToOne
#JoinColumn(name = "order_type_id")
private OrderType orderType;
public Long getOrderTypeId(){
return orderType != null ? orderType.getId() : null;
}
}
Failing that you could add a listener for an AfterCreateEvent
https://docs.spring.io/spring-data/rest/docs/current/reference/html/#events
and either set the value manually on this listener based on the associated OrderType or, inject an EntityManager to your listener and trigger a refresh on the saved instance:
https://docs.oracle.com/javaee/6/api/javax/persistence/EntityManager.html#refresh(java.lang.Object)
#RepositoryEventHandler
public class OrderEventHandler {
#PersistenceContext
private EntityManager em;
#HandleAfterCreate
public void onOrderCreated(Order o) {
em.refresh(o);
}
}

Categories