How to save a raw JSON in the MySQL using JPA - json

I need to save this JSON in MySQL database, and later recover it and display in the frontend
I'm using Spring boot with JPA, but i have been facing errors about serialization when the Request comes from frontend, like this:
I send the json from the frontend to my controller:
#RestController
#RequestMapping("/theoriclesson")
#CrossOrigin
public class TheoricLessonController implements ITheoricLessonController {
#Autowired
private TheoricLessonRepository theoricLessonRepository;
#Autowired
private ModuleRepository moduleRepository;
#Override
#PostMapping("/")
public void add(#RequestBody TheoricLesson theoricLesson) {
Module module = new Module();
module.setName("Modulo de teste");
TheoricLesson tc = new TheoricLesson();
Module mP = moduleRepository.save(module);
tc.setModule(mP);
theoricLessonRepository.save(tc);
}
}
and my entities:
#Data
#Entity
#Builder
public class TheoricLesson {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long xpEarned;
private String lessonName;
#Embedded
private DraftJsText draftJsText;
#ManyToOne
#JoinColumn(name = "module_id")
#JsonIgnore
private Module module;
}
#Getter
#Setter
#ToString
#Embeddable
public class DraftJsText {
public DraftJsText() {
}
public DraftJsText(String blocks, String entityMap) {
this.blocks = blocks;
this.entityMap = entityMap;
}
#Column(columnDefinition = "JSON")
private String blocks;
#Column(columnDefinition = "JSON")
private String entityMap;
}

Related

JPA: Many to many relationship - JsonMappingException: Infinite recursion

I'm having trouble with a many to many relation with JPA.
My code looks as follows:
The Sensor class:
#Entity
#Table(name = "sensor")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Sensor {
#Id
private long chipId;
#OneToMany(mappedBy = "sensor")
#JsonBackReference
private Set<Link> userLinks;
private String firmwareVersion;
private long creationTimestamp;
private String notes;
private long lastMeasurementTimestamp;
private long lastEditTimestamp;
private double gpsLatitude;
private double gpsLongitude;
private double gpsAltitude;
private String country;
private String city;
private boolean indoor;
private boolean published;
}
The user class:
#Entity
#Table(name = "user")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonManagedReference
private int id;
private String firstName;
private String lastName;
private String email;
private String password;
#OneToMany(mappedBy = "user")
private Set<Link> sensorLinks;
private int role;
private int status;
private long creationTimestamp;
private long lastEditTimestamp;
}
And the Link class (relation class):
#Entity
#Table(name = "link")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Link {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne
#JoinColumn(name = "user_id")
#MapsId("user_id")
private User user;
#ManyToOne
#JoinColumn(name = "sensor_id")
#MapsId("sensor_id")
private Sensor sensor;
private boolean owner;
private String name;
private int color;
private long creationTimestamp;
}
The controller:
...
#RequestMapping(method = RequestMethod.GET, path = "/user/{email}", produces = MediaType.APPLICATION_JSON_VALUE)
#ApiOperation(value = "Returns details for one specific user")
public User getUserByEmail(#PathVariable("email") String email) {
return userRepository.findByEmail(email).orElse(null);
}
...
The UserRepository:
public interface UserRepository extends JpaRepository<User, Integer> {
Optional<User> findByEmail(String email);
#Modifying
#Query("UPDATE User u SET u.firstName = ?2, u.lastName = ?3, u.password = ?4, u.role = ?5, u.status = ?6 WHERE u.id = ?1")
Integer updateUser(int id, String firstName, String lastName, String password, int role, int status);
}
I want to achieve, that the user endpoint shows all linked sensors with that particular user.
What I get is only an error message:
JSON mapping problem:
com.chillibits.particulatematterapi.model.db.main.User["sensorLinks"];
nested exception is
com.fasterxml.jackson.databind.JsonMappingException: Infinite
recursion (StackOverflowError) (through reference chain:
com.chillibits.particulatematterapi.model.db.main.User["sensorLinks"])
How can I fix this issue?
Thanks in advance
Marc
------------------------------------ Edit -----------------------------------
According to Abinash Ghosh's answer, I added following DTOs:
UserDto:
#Data
#NoArgsConstructor
#AllArgsConstructor
public class UserDto {
private int id;
private String firstName;
private String lastName;
private Set<LinkDto> sensorLinks;
private int role;
private int status;
private long creationTimestamp;
private long lastEditTimestamp;
}
LinkDto:
#Data
#NoArgsConstructor
#AllArgsConstructor
public class LinkDto {
private Integer id;
private SensorDto sensor;
private boolean owner;
private String name;
private int color;
private long creationTimestamp;
}
And the mapper (I realized it a bit different, but it should be the same):
public UserDto getUserByEmail(#PathVariable("email") String email) {
User user = userRepository.findByEmail(email).orElse(null);
return convertToDto(user);
}
private UserDto convertToDto(User user) {
return mapper.map(user, UserDto.class);
}
This leads to following Exception:
2020-04-13 14:22:24.383 WARN 8176 --- [nio-8080-exec-2] o.h.e.loading.internal.LoadContexts : HHH000100: Fail-safe cleanup (collections) : org.hibernate.engine.loading.internal.CollectionLoadContext#68ab57c7<rs=HikariProxyResultSet#2017009664 wrapping Result set representing update count of -1>
1) Error mapping com.chillibits.particulatematterapi.model.db.main.User to com.chillibits.particulatematterapi.model.io.UserDto
1 error] with root cause
java.lang.StackOverflowError: null
at com.mysql.cj.NativeSession.execSQL(NativeSession.java:1109) ~[mysql-connector-java-8.0.19.jar:8.0.19]
...
It's working!
This post helped: https://stackoverflow.com/a/57111004/6296634
Seems that you should not use Lombok #Data in such cases.
When User serialized for the response, all getter methods of User's fields are called.
So, User relational field sensorLinks's getter are also called to set value. This happened recursively. That's cause of infinite recursion.
It's better to not use Entity as a response. Create a DTO class for User then map User entity value into DTO then send response. Don't use any Enity class again into DTO then it will result same problem
For dynamically map one model to another you can use ModleMapper
public class UserDTO {
//Fields you want to show in response & don't use enity class
private Set<LinkDTO> sensorLinks;
}
public class LinkDTO{
//Fields you want to show in response &don't use enity class
}
public User getUserByEmail(#PathVariable("email") String email) {
User user = userRepository.findByEmail(email).orElse(null);
UserDTO userDto = merge(user,UserDTO.class)
return userDto;
}
public static <T> void merge(T source, T target) {
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
modelMapper.map(source, target);
}

Spring boot MongoDB configuration

I'm working in a spring boot application & use both MySQL & mongodb as databases.Below you can see spring boot main application for MySQL.
#EnableFeignClients(basePackages = {"com.saman.kamal.nimalservice"})
#EnableDiscoveryClient
#SpringBootApplication(scanBasePackages = {"com.saman.kamal.nimalservice"})
#EnableOAuth2Client
#EnableJpaRepositories(basePackages = {"com.saman.kamal.nimalservice.repository"})
#EntityScan(basePackages = {"com.saman.kamal.nimalservice.domain"})
public class HuththaApplication {
public static void main(String[] args) {
SpringApplication.run(HuththaApplication.class, args);
}
}
if I use mongodb instead of mysql
#EntityScan(basePackages = {"com.saman.kamal.nimalservice.domain"})
should be change. How should be change?
Below you can see my entity class.
import java.io.Serializable;
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#ToString
#Document
public class EbankLog implements Serializable {
private static final Long serialVersionUID = 1L;
#Id
private String bothala;
private String sapaththu;
private String sereppu;
private String kanda;
private String bag;
}
You have not let Spring know where your MongoDb repositories are. In order to do that use #EnableMongoRepositories. See below codes:
#EnableMongoRepositories(basePackages = "your.mongodb.repositories.package")
#EnableFeignClients(basePackages = {"com.saman.kamal.nimalservice"})
#EnableDiscoveryClient
#SpringBootApplication(scanBasePackages = {"com.saman.kamal.nimalservice"})
#EnableOAuth2Client
#EnableJpaRepositories(basePackages = {"com.saman.kamal.nimalservice.repository"})
#EntityScan(basePackages = {"com.saman.kamal.nimalservice.domain"})
public class HuththaApplication {
public static void main(String[] args) {
SpringApplication.run(HuththaApplication.class, args);
}
}

Could not write JSON: Infinite recursion (StackOverflowError); nested exception spring boot

This is my District Controller, when I try to fetch data after saving I get the error, even when I try get object form getDistrict(Long id) the same strikes please suggest some way, am very new at spring environment:
package com.gad.services;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.gad.repositories.DistrictMasterRepositories;
import com.gad.rmodels.Districtmaster;
import com.gad.rmodels.Statemaster;
#Service
public class DistricMasterServices {
#Autowired
DistrictMasterRepositories districtMasterRepositories;
#Autowired
StateMasterServices stateMasterServices;
List<Districtmaster> districtmaster;
public Iterable<Districtmaster> savenewdistrict(Long id,Districtmaster districtmaster_rec){
System.out.println(id);
Statemaster statemaster=null;
statemaster = stateMasterServices.getStateById(id);
System.out.println("savenewdistrict");
districtmaster_rec.setStatemaster(statemaster);
districtMasterRepositories.save(districtmaster_rec);
Iterable<Districtmaster>districtmaster2 = districtMasterRepositories.findAll();
return districtmaster2;
}
public Districtmaster getDistrict(Long id){
Districtmaster districtmaster = districtMasterRepositories.findOne(id);
return districtmaster;
}
}
The model class for state:
package com.gad.rmodels;
import static javax.persistence.GenerationType.SEQUENCE;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
/**
* Statemaster generated by hbm2java
*/
#Entity
#Table(name="statemaster"
,schema="aop_gad_v1"
)
public class Statemaster implements java.io.Serializable {
private long id;
private String stateName;
private Set<Districtmaster> districtmasters = new HashSet<Districtmaster>(0);
public Statemaster() {
}
public Statemaster(long id) {
this.id = id;
}
public Statemaster(long id, String stateName, Set<Districtmaster> districtmasters) {
this.id = id;
this.stateName = stateName;
this.districtmasters = districtmasters;
}
#SequenceGenerator(name="generator_statemasterid", sequenceName="aop_gad_v1.gad_statemaster_seq")
#Id
#GeneratedValue(strategy=SEQUENCE, generator="generator_statemasterid")
#Column(name="id", unique=true, nullable=false)
public long getId() {
return this.id;
}
public void setId(long id) {
this.id = id;
}
#Column(name="state_name", length=20)
public String getStateName() {
return this.stateName;
}
public void setStateName(String stateName) {
this.stateName = stateName;
}
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY, mappedBy="statemaster")
public Set<Districtmaster> getDistrictmasters() {
return this.districtmasters;
}
public void setDistrictmasters(Set<Districtmaster> districtmasters) {
this.districtmasters = districtmasters;
}
}
Distric model:
package com.gad.rmodels;
import static javax.persistence.GenerationType.SEQUENCE;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
/**
* Districtmaster generated by hbm2java
*/
#SuppressWarnings("serial")
#Entity
#Table(name="districtmaster",schema="aop_gad_v1")
public class Districtmaster implements java.io.Serializable {
private long id;
private Statemaster statemaster;
private String districtName;
private Set<GadGuestHouseMaster> gadGuestHouseMasters = new HashSet<GadGuestHouseMaster>(0);
public Districtmaster() {
}
public Districtmaster(long id) {
this.id = id;
}
public Districtmaster(long id, Statemaster statemaster, String districtName, Set<GadGuestHouseMaster> gadGuestHouseMasters) {
this.id = id;
this.statemaster = statemaster;
this.districtName = districtName;
this.gadGuestHouseMasters = gadGuestHouseMasters;
}
#SequenceGenerator(name="generator_districtmasterid", sequenceName="aop_gad_v1.gad_districtmasterid_seq")
#Id
#GeneratedValue(strategy=SEQUENCE, generator="generator_districtmasterid")
#Column(name="id", unique=true, nullable=false)
public long getId() {
return this.id;
}
public void setId(long id) {
this.id = id;
}
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="district_of_state")
public Statemaster getStatemaster() {
return this.statemaster;
}
public void setStatemaster(Statemaster statemaster) {
this.statemaster = statemaster;
}
#Column(name="district_name", length=20)
public String getDistrictName() {
return this.districtName;
}
public void setDistrictName(String districtName) {
this.districtName = districtName;
}
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY, mappedBy="districtmaster")
public Set<GadGuestHouseMaster> getGadGuestHouseMasters() {
return this.gadGuestHouseMasters;
}
public void setGadGuestHouseMasters(Set<GadGuestHouseMaster> gadGuestHouseMasters) {
this.gadGuestHouseMasters = gadGuestHouseMasters;
}
}
The Error I get:
[{"timestamp":1512641978311,"status":200,"error":"OK","exception":"org.springframework.http.converter.HttpMessageNotWritableException","message":"Could
not write JSON: Infinite recursion (StackOverflowError); nested
exception is com.fasterxml.jackson.databind.JsonMappingException:
Infinite recursion (StackOverflowError) (through reference chain:
com.gad.rmodels.Statemaster[\"districtmasters\"]->org.hibernate.collection.internal.PersistentSet[0]-
You are facing this issue because the Statemaster model contains the object of Districtmaster model, which itself contains the object of Statemaster model. This causes an infinite json recursion.
You can solve this issue by 3 methods.
1 - Create a DTO and include only the fields that you want to display in the response.
2 - You can use the #JsonManagedReference and #JsonBackReference annotations.
E.g. Add the #JsonManagedReference annotation to the Statemaster model.
#JsonManagedReference
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY, mappedBy="statemaster")
public Set<Districtmaster> getDistrictmasters() {
return this.districtmasters;
}
Add the #JsonBackReference annotation to the Districtmaster model.
#JsonBackReference
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="district_of_state")
public Statemaster getStatemaster() {
return this.statemaster;
}
3 - You can use the #JsonIgnore annotation on the getter or setter method.
#JsonIgnore
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY, mappedBy="statemaster")
public Set<Districtmaster> getDistrictmasters() {
return this.districtmasters;
}
However, this approach will omit the set of Districtmaster from the response.
I've been struggling with the same problem for days, I tried #JsonIgnore, #JsonManagedReference and #JsonBackReference annotation, and even #JsonIdentityInfo annotation and none of them have worked.
If you (the future readers ) are in the same situation, the solution is easier than you expected, simply put #JsonIgnore or #JsonManagedReference / #JsonBackReference on the attribute's getter and not on the attribute itself. And that will do.
Here's a simple example how to do so :
Say we have two classes, Order and Product, and OneToMany relation between them.
Order class
public class Order{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private String id_order;
private double price;
#OneToMany(mappedBy = "order")
#LazyCollection(LazyCollectionOption.FALSE)
private List<Product> products
//constructor, getters & setter
}
Product Class:
public class Product{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private String id_product;
private String name;
#ManyToOne
#JoinColumn(name = "id_order")
private Order order;
//consturctor, getters & setters
}
So in order to use #JsonManagedReference and #JsonBackReference, just add them to the getters as the following :
public class Order{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private String id_order;
private double price;
#OneToMany(mappedBy = "order")
#LazyCollection(LazyCollectionOption.FALSE)
private List<Product> products
//constructor, getters & setter
#JsonManagedReference
public List<Product> getProducts(){
return products;
}
Product class:
public class Product{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private String id_product;
private String name;
#ManyToOne
#JoinColumn(name = "id_order")
private Order order;
//consturctor, getters & setters
#JsonBackReference
public Order getOrder(){
return order;
}
}
#JsonBackReference and #JsonManagedReference didn't work for me since I was using a class that represented a join table and the only way to get it to work was to put the #JsonBackReference above the fields representing the linked classes. The net effect was that if I was pulling a record from the join class using JSON, none of the join fields would show up, making the returned information useless.
I'd give examples but I like to avoid long replies. #JsonIdentityInfo explained in the article below provides a short, simple but highly efficient solution.
Bidirectional relationships and InfiniteRecursion
That's because for Statemaster in json the set of Districtmaster's is put.
And each Districtmaster has the Statemaster in itself, so it's also put into the json. So that you get the infinite recursion
#JsonIgnore
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY,
mappedBy="statemaster")
public Set<Districtmaster> getDistrictmasters() {
return this.districtmasters;
}
Adding #JsonIgnore annotation on Set<Districtmaster> will prevent that recursion.
You can put the #JsonIgnore at public Statemaster getStatemaster() either.
This issue occurs when dealing with bi-directional mapping. Use #JsonManagedReferenc and #JsonBackReference in DAO/Entity class
#JsonManagedReference is the forward part of reference – the one that gets serialized normally.
#JsonBackReference is the back part of reference – it will be omitted from serialization.
(Read More)
In the Statemaster class make districtmasters a List<Districtmaster> instead of Set<Districtmaster> and change the getter method accordingly: public List<Districtmaster> getDistrictmasters().
Unfortunately I can not explain why, but this worked for me.
Use List instead of Set. This is how I solved my issue.
If you are using Lombok:
The problem may be due to using Lombok and Set together.
Lombok creates an equals method that is used by Set in Java to determine whether two objects are the same or not. The equals method that Lombok generates calls the equals method of another class, which in turn calls back the equals method of the first class, thus generating infinite recursion.
To solve the problem, you need either to use List instead of Set, or to override the equals and hashCode methods (so Lombok will not generate them).
I've found out this trough another StackOverflow question.
Looks like your problem is Hibernate relations. When you try to serialize the entity Statemaster the serializer calls serialization of the Districtmaster set which in turn somehow reference the Statemaster again.
There are two possible ways to solve:
Unproxy object
Create DTO (Data Transfer Object) - kind of copy of your entity
where all necessary fields should be assigned and return the DTO.
There are 2 different ways to solve the problem:
You need to implement Serializable on all the #Data classes/DTOs being used, and make sure to generate a private static final long serialVersionUID for the class. Advantage: you may use "lombok" #Data/#Getter/#Setter annotations this way.
Put #JsonIgnore on every getter/setter in all the classes/DTOs being used. Disadvantage: using "lombok" #Data/#Getter/#Setter annotations may be problematic.
Error message i got in my project
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion
In my case, i also faced this same problem to solve this I just use #JsonIgnore, and it's working fine. Then I replace #JsonIgnore with #JsonBackReference also worked perfectly.
To understand the scenario please check the Project, Spouse, Address, and Employee Class
Address Class
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonIgnore;
#Getter
#Setter
#ToString
#NoArgsConstructor
#EqualsAndHashCode
#Entity
#Table(name = "address")
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String line1;
private String line2;
private String zipCode;
private String city;
private String state;
private String country;
#JsonIgnore
//#JsonBackReference
#ManyToOne
private Employee employee;
public Address(String line1, String line2, String zipCode, String city, String state, String country, Employee employee) {
this.line1 = line1;
this.line2 = line2;
this.zipCode = zipCode;
this.city = city;
this.state = state;
this.country = country;
this.employee = employee;
}
}
Employee Class
#Getter
#Setter
#NoArgsConstructor
#ToString
#EqualsAndHashCode
#Entity
#Table(name = "employee")
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
private Integer employeeId;
private String employeeName;
private String employeeCity;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "fk_spouse")
private Spouse spouse;
#OneToMany(cascade = CascadeType.ALL)
private List<Address> addresses;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "employee_project",
joinColumns = #JoinColumn(name = "fk_employee"),
inverseJoinColumns = #JoinColumn(name = "fk_project"))
private List<Project> projects;
public Employee(String employeeName, String employeeCity, Spouse spouse, List<Address> addresses, List<Project> projects) {
this.employeeName = employeeName;
this.employeeCity = employeeCity;
this.spouse = spouse;
this.addresses = addresses;
this.projects = projects;
}
public void removeProject(Project project){
this.projects.remove(project);
project.getEmployees().remove(project);
}
public void addProject(Project project){
this.projects.add(project);
project.getEmployees().add(this);
}
}
Project class
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.*;
import javax.persistence.*;
import java.util.List;
#Getter
#Setter
#ToString
#EqualsAndHashCode
#NoArgsConstructor
#Entity
#Table(name = "project")
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String clientName;
#JsonIgnore
// #JsonBackReference
#ManyToMany(mappedBy = "projects")
private List<Employee> employees;
public Project(String name, String clientName, List<Employee> employees) {
this.name = name;
this.clientName = clientName;
this.employees = employees;
}
}
Spouse Class
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.*;
import javax.persistence.*;
#Getter
#Setter
#NoArgsConstructor
#ToString
#EqualsAndHashCode
#Entity
#Table(name = "spouse")
public class Spouse {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String mobileNo;
private Long age;
// #JsonBackReference
#JsonIgnore
#OneToOne(mappedBy = "spouse")
private Employee employee;
public Spouse(String name, String mobileNo, Long age, Employee employee) {
this.name = name;
this.mobileNo = mobileNo;
this.age = age;
this.employee = employee;
}
}
I know both #JsonIgnore and #JsonManagedReference, #JsonBackReference are used to solve the Infinite recursion (StackOverflowError)
Answer
These are Jackson Annotations.
#JsonIgnore is not created to resolve the Infinite recursion (StackOverflowError) problem, only ignores the property of serialization and deserialization, Just avoid the infinite recursion.
Moreover, #JsonManagedReference and #JsonBackReference are created to handle two ways linkage between fields, if you don't need those properties in the serialization or deserialization process, you can use #JsonIgnore. Otherwise, using the #JsonManagedReference /#JsonBackReference pair is the way to go.

Orika Bean Mapper Spring Data ManyToMany join table overriding

In my Spring project I have domain and model layer separately. Domain layer are just entities, that maps to tables in MySQL db. Models are used in service layer. I have two tables: User and Roles, that have ManyToMany relationship.
#Entity
#Table(name = "user")
public class UserEntity {
#Id
#GeneratedValue
private Long id;
private String name;
#ManyToMany(mappedBy="users", cascade=CascadeType.ALL,fetch=FetchType.EAGER)
private List<RoleEntity> roles;
//..Getters Setters
}
#Entity
#Table(name = "role")
public class RoleEntity {
#Id
#GeneratedValue
private Long id;
private String value;
#ManyToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
#JoinTable(name = "user_role",
joinColumns = #JoinColumn(name = "role_id") , inverseJoinColumns = #JoinColumn(name = "user_id") )
private List<UserEntity> users;
}
Model
public class User {
private Long id;
private String name;
private List<Role> roles;
}
public class Role {
private Long id;
private String value;
private List<User> users;
}
Repository Layer
#Repository
public interface UserRepo extends JpaRepository<UserEntity, Long>{
}
Service
#Service
public class UserService {
#Autowired
private UserRepo userRepo;
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
MapperFacade mapper = mapperFactory.getMapperFacade();
public void save(User user) {
userRepo.save(mapper.map(user, UserEntity.class));
}
public User getUser(Long id) {
return mapper.map(userRepo.findOne(id), User.class);
}
}
The problem is - each time I retrieve user, and update it properties (name for example), and than save back it into table, row in the join table user_role, overrides too. For example if it was
id=1, user_id=1, role_id=1
, than after update it becomes
id=2, user_id=1, role_id=1
.
#Test
public void contextLoads() {
User user = userService.getUser(1L);
user.setName("kyk");
userService.save(user);
}
It works correct without mapper, so mapper is the reason. And I can't get any workaround. Has any one faced the same problem? Will be thankful for any help.

JsonIgnore and JsonBackReference are being Ignored

i'm using spring 4.0.1 , hibernate 4.3.5 ,jackson 1.9.2 and STS IDE
I'm creating a RESTful webservice that returns a data in JSON format
when i use Hibernate code generator it generates getters and setter of associated entities annotated by #OneToMany(fetch = FetchType.LAZY, mappedBy = "user") for the source
and #ManyToOne(fetch = FetchType.LAZY) for the reference
which causes an infinite recursion during serialization. I tried using Jackson's #JsonIgnore and #JsonBackReference annotations to fix the problem but it seems as if they are being totally ignored and the infinite recursion is still occurring.
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError)
This is my entity classes
User.class
//i get that suggestion from some sites
#JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" })
#Entity
#Table(name = "user", catalog = "someSchema")
public class User implements java.io.Serializable {
private String name;
private String password;
private String username;
private Set<Telephone> telephones = new HashSet<Telephone>(0);
#JsonManagedReference
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
public Set<Telephone> getTelephones() {
return this.telephones;
}
public void setTelephones(Set<Telephone> telephones) {
this.telephones = telephones;
}
}
Telephone.class
#Entity
#Table(name = "telephone", catalog = "someSchema")
public class Telephone implements java.io.Serializable {
private User user;
private String telephone;
#ManyToOne(fetch = FetchType.LAZY)
//tried #JsonIgnore only and both
#JsonIgnore
//tried #JsonBackReference only and both
#JsonBackReference
#JoinColumn(name = "user_id", nullable = false)
public User getUser() {
return this.user;
}
#JsonIgnore
#JsonBackReference
public void setUser(User user) {
this.user = user;
}
}
concerning registering jackson to my application, i used xml config
<mvc:annotation-driven>
<mvc:message-converters>
<bean
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean
class="web.jsonConverters.HibernateAwareObjectMapper" />
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
and mapper class
public class HibernateAwareObjectMapper extends ObjectMapper {
public HibernateAwareObjectMapper() {
Hibernate4Module hm = new Hibernate4Module();
registerModule(hm);
}
}
Do you have any idea why the Jackson annotations are being ignored?
any help will be appreciated...
use
import com.fasterxml.jackson.annotation.JsonBackReference;
instead of
import org.codehaus.jackson.annotate.JsonBackReference;
i found a way by annotating the setter by #Transient
idont know why but it works fine
User.class
//i get that suggestion from some sites
#JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" })
#Entity
#Table(name = "user", catalog = "someSchema")
public class User implements java.io.Serializable {
private String name;
private String password;
private String username;
private Set<Telephone> telephones = new HashSet<Telephone>(0);
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
public Set<Telephone> getTelephones() {
return this.telephones;
}
public void setTelephones(Set<Telephone> telephones) {
this.telephones = telephones;
}
}
Telephone.class
#Entity
#Table(name = "telephone", catalog = "someSchema")
public class Telephone implements java.io.Serializable {
private User user;
private String telephone;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", nullable = false)
public User getUser() {
return this.user;
}
#Transient
public void setUser(User user) {
this.user = user;
}
}
Another Naive Solution
I solved it manually in my RESTful Controller
by loop over the set of telephones and set user to null
#RestController
#RequestMapping("/user")
public class UserController extends ParentController {
static final Logger logger = Logger.getLogger(UserController.class.getName());
#Autowired
IUserDao iuserdao;
#RequestMapping(value = "/signin", method = RequestMethod.POST)
public ResponseEntity<User> signin(#RequestBody LoginWrapper login) {
System.out.println("==============GET USER==============");
try {
User user = iuserdao.signin(login);
if (user == null) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set(ERR_HEADER_NAME, "user not exist");
return new ResponseEntity<User>(httpHeaders, HttpStatus.NOT_FOUND);
} else {
List<Telephone> tels=user.getTelephones();
for (Telephone telephone : tels) {
telephone.setUser(null);
}
return new ResponseEntity<User>(user, HttpStatus.OK);
}
} catch (Exception e) {
System.out.println(e.getMessage());
return null;
}
}
still need a better answer concerning Jackson problem..