The Problem
We have two entities with one-to-many relationships which references each other, but it failed with the exception:
com.fasterxml.jackson.databind.JsonMappingException: Already had POJO for id
The Fix
To easily troubleshoot the issue, I created a sample class like below:
new department: {"departmentId":"e3e0e676-0c52-493d-8f49-bedde05cbb11","name":"newD1","employees":[{"employeeId":"6b7bbbec-8be6-4423-a4ef-af7924df177b","name":"e1","department":{"departmentId":"e3e0e676-0c52-493d-8f49-bedde05cbb11","name":"oldD1","employees":["6b7bbbec-8be6-4423-a4ef-af7924df177b"]}}]}
Miscs
We need exclude employees from Department's toString: @ToString(exclude = "employees")
- Otherwise it would throw java.lang.StackOverflowError
Likewise, we need exclude employees from @EqualsAndHashCode.
We have two entities with one-to-many relationships which references each other, but it failed with the exception:
com.fasterxml.jackson.databind.JsonMappingException: Already had POJO for id
The Fix
To easily troubleshoot the issue, I created a sample class like below:
@Data
@Accessors(chain = true)
@EqualsAndHashCode(of = {"employeeId"}, callSuper = false)
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "employeeId")
public static class Employee {
private UUID employeeId;
private String name;
private Department department;
}
@Data
@Accessors(chain = true)
@EqualsAndHashCode(of = {"departmentId"}, callSuper = false)
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "departmentId")
@ToString(exclude = "employees")
private static class Department {
private UUID departmentId;
private String name;
private Set<Employee> employees;
public Set<Employee> getEmployees() {
if (employees == null) {
employees = new HashSet<>();
}
return employees;
}
}
public static void main(String[] args) throws IOException {
Employee e1 = new Employee().setEmployeeId(UUID.randomUUID()).setName("e1");
Department d1 =
new Department().setDepartmentId(UUID.randomUUID()).setName("oldD1").setEmployees(Sets.newHashSet(e1));
e1.setDepartment(d1);
ObjectMapper objectMapper = new ObjectMapper();
String departmentStr = objectMapper.writeValueAsString(d1);
objectMapper.writeValueAsString(e1);
Department oldD1 = objectMapper.readValue(departmentStr, Department.class);
Department newD1 = new Department().setDepartmentId(d1.getDepartmentId()).setName("newD1");
newD1.getEmployees().addAll(oldD1.getEmployees());
// without the following statements: it will throw
// com.fasterxml.jackson.databind.JsonMappingException: Already had POJO for id
// for (Employee e : oldD1.getEmployees()) {
// e.setDepartment(newD1);
// }
departmentStr = objectMapper.writeValueAsString(newD1);
System.out.println("new department: " + departmentStr);
// now read it back will throw sonMappingException: Already had POJO for id
Department newNewD1 = objectMapper.readValue(departmentStr, Department.class);
System.out.println("---" + newNewD1);
}
This reproduces the issue, and from the output:new department: {"departmentId":"e3e0e676-0c52-493d-8f49-bedde05cbb11","name":"newD1","employees":[{"employeeId":"6b7bbbec-8be6-4423-a4ef-af7924df177b","name":"e1","department":{"departmentId":"e3e0e676-0c52-493d-8f49-bedde05cbb11","name":"oldD1","employees":["6b7bbbec-8be6-4423-a4ef-af7924df177b"]}}]}
I found that after I changed the department to newD1, the employee still refers to old department object with department name: oldD1.
This leads to my fix like below: after I made change to the department object, make sure the employees refers to the new department object.
// without the following statements: it will throw
// com.fasterxml.jackson.databind.JsonMappingException: Already had POJO for id
for (Employee e : oldD1.getEmployees()) {
e.setDepartment(newD1);
}
We need exclude employees from Department's toString: @ToString(exclude = "employees")
- Otherwise it would throw java.lang.StackOverflowError
Likewise, we need exclude employees from @EqualsAndHashCode.