I know this has been asked a lot of times, and the solution is pretty obvious, but in my case it doesn't really work, and I can't figure out how to solve it.
problem: Could not write JSON: Infinite recursion (StackOverflowError)
The setup is like this:
Employee belongs to a Department. (ManyToOne)
Department has a Employee as Manager. (OneToOne)
I didn't want to have a #OneToMany List in Department so the owning side is missing.
Employee has a Department object, but this is the Department it belongs to, not the Department he manages.
Employee.java
#Entity
#Table(name = "ts_employee")
//#JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class,property="#emp_id")
public class Employee extends AbstractEntity {
#ManyToOne
#JoinColumn(name = "dept_id")
#JsonManagedReference
private Department department;
... getters and setters
}
Department.java
#Entity
#Table(name = "ts_department")
//#JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="#dept_id")
public class Department extends AbstractEntity {
#OneToOne
#JoinColumn (name = "manager_id")
#JsonBackReference
private Employee manager;
.. other fields.. getters and setters
}
AbstractEntity.java
#MappedSuperclass
public class AbstractEntity implements Serializable {
#Id
#GeneratedValue()
private Long id;
... getters and setters
}
I've tried both solutions:
#JsonBackReference + #JsonManagedReference
I get rid of the StackOverflow, but the Department.manager is not serialized (#JsonBackReference), and not sent in the response, which in bad.
#JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="#emp_id"), which doesn't seem to do anything, and StackOverflow is thrown in my face :(
How can I solve this, hopefully without modifying the model classes?
Thanks a lot :)
The #JsonBackReference wont be serialized.
If possible try to use #JsonIdentityInfo over #JsonManagedReference and #JsonBackReference. Follow this link for documentation.
You can also try using #JsonIgnore if you don't need to maintain the relationship further in the process.
Related
I am using Spring Boot and Jackson and Hibernate to create an API. Hibernate connects to a MySQL database.
I understand the good practices but I'm stuck on a particular point.
I have an n:m relationship that contains an extra field.
Ex: Author(id, ...) -> Written(idAuthor, idBook, date) <- Book(id, ...)
I understand how to map a traditional n:m relationship, but this technique does not apply to me this time.
For this, I found a source on the internet that showed the solution: create an intermediate class in my code that contains an Author type object and a Book type object + my additional fields.
#Entity
#Table(name = "Author")
public class Author implements Serializable {
/...
#Id
#GeneratedValue
private int id;
#OneToMany(mappedBy = "author", cascade = CascadeType.ALL)
private Set<Written> written= new HashSet<>();
/...
}
#Entity
#Table(name = "Book")
public class Book implements Serializable{
/...
#Id
#GeneratedValue
private int id;
#OneToMany(mappedBy = "book", cascade = CascadeType.ALL)
private Set<Written> written= new HashSet<>();
/...
}
public class Written implements Serializable {
#Id
#ManyToOne
#JoinColumn(name = "idAuthor")
private Author author;
#Id
#ManyToOne
#JoinColumn(name = "idBook")
private Book book;
//Extra fields ....
}
That's a bidirectional link.
With this code, I get an infinite recursivity error:
Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: java.util.ArrayList[0]->com.exampleAPI.api.model.Book["written"])]
I tried to use #JsonIgnore, #JsonManagedReference and #JsonBackReference on the Written class, also tried to use transient keyword, but nothing worked.
I can't find any source on the internet that could help me, and neither can the documentation for this particular case.
Can someone help me?
When unhandled bidirectional relationship occurs, Jackson faces infinite recursion.
I tried to use #JsonIgnore, #JsonManagedReference and #JsonBackReference on the Written class
You need to use #JsonManagedReference and #JsonBackReference annotations separately to prevent these cycles between Book and Written. A side note, transient has nothing to do with the persistence but the serialization. JPA works with the #Transient annotation.
public class Book implements Serializable {
#OneToMany(mappedBy = "book", cascade = CascadeType.ALL)
#JsonBackReference
private Set<Written> written= new HashSet<>();
...
}
public class Written implements Serializable {
#Id
#ManyToOne
#JoinColumn(name = "idBook")
#JsonManagedReference
private Book book;
...
}
Important: Don't send database entities through REST (probably what you are up to do). Better create a DAO object without bidirectional relationship and map entities into DAOs. There are several libraries able to do that: I highly recommend MapStruct, however ModelMapper is also an option. If there is a lower number of such entities, using constructors/getters/setters would be enough.
I have a user entity with an assistant column.
Every user has an assistant but there are circles as well.
For example : User A's assistant is User B and User B's assistant is
user A.
If I use #ManyToOne and #OneToMany annotations, then, there is an infinite recursion when converting objects to JSON, even #JsonManagedReference and
#JsonBackReference didn't help.
BaseEntity:
#MappedSuperclass
#Data
public class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
#Version
private int version;
}
User:
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Data
#EqualsAndHashCode(callSuper = true)
#Table(name = "Users")
public class User extends BaseEntity {
#Column
private String username;
#Column
private String name;
#JsonManagedReference
#ManyToOne
#JoinColumn(name = "assistant_id")
private User assistant;
#JsonBackReference
#OneToMany(mappedBy = "assistant")
private Set<User> assistants;
}
Are there any opportunity in Spring to solve this?
#JsonManagedReference/#JsonBackReference won't help, because the 'forward' references can still form a cycle, even when the 'inverse' references are not being serialized.
What you want is probably for the User assigned to the assistant property to be serialized without its own assistant property (so that any cycles break). Essentially, you have the same issue as here, except in your case A and B are the same class.
Apart from the solution described in the question I've linked to, you'll also want to specify which #JsonView to use when serializing the object. See the 'Using JSON Views with Spring' section here.
Could you create Assistant entity based on the same table and join?
I have a group of tables, that are all identical apart from their owner table, and the corresponding foreign keys to that table. I made it all generic thanks to Hibernate/JPA, but cannot pass the #JoinColumn information via #AssociationOverride since the name value for it is ignored, or not overridden at all.
For example;
#Data
#Entity
#Table(name = "etc")
#AssociationOverride(name = "parent", joinColumns = #JoinColumn(name = "id"))
public class RealEntity extends BaseEntity<ParentEntity, String> {
}
with;
#Data
#MappedSuperClass
public class BaseEntity<K, P> implements Serializable {
#EmbeddedId
protected Key<K> key = new Key<>();
#MapsId("fk")
#ManyToOne
#JsonBackReference
protected P parent;
#Data
#Embeddable
public static class Key<K> implements Serializable {
protected K fk;
#Column(name = "sub_id")
protected String subId;
}
}
parent:
#Data
#Entity
#Table(name = "parentc")
public class ParentEntity implements Serializable {
#Column(name = "id")
protected String parentId;
}
So as you see, it works well except for the parent's fk reference definition, I get mapping error since Hibernate tries to find a parent_id, rather than just id for the foreignKey, since #JoinColumn override is ignored. It works if I put the parent information in the RealEntity directly (obviously), or if I put #JoinColumn(name = "id") on parent in BaseEntity but I want to keep it as generic as possible. Is there any solution to this issue? Or should I just give up?
edit: it seems when I put a proper #JoinColumn with acceptable mapping for joining on parent in BaseEntity, that does get overridden, so it needs something valid to override. I cannot just add an association from nothingness is that the case? I've seen many examples on the web where they were putting associations from scratch, my usage of #MapsId, might be breaking the usage I guess. But I cannot change my current structure, since it is necessary to be able to represent composite foreign key definition for dependent child tables... I feel like there is a very simple solution, or some hacky way to achieve what I want, and I cannot seem to find it!
#Entity
public class Person {
#Id
#GeneratedValue
private int personId;
#OneToOne(cascade=CascadeType.ALL, mappedBy="person", fetch=FetchType.LAZY)
private PersonDetail personDetail;
//getters and setters
}
#Entity
public class PersonDetail {
#Id
#GeneratedValue
private int personDetailId;
#OneToOne(cascade=CascadeType.ALL,fetch=FetchType.LAZY)
private Person person;
//getters and setters
}
when i do
Person person1=(Person)session.get(Person.class, 1);
i see two queries being fired. one for fetching person data and another for person detail data.
As per my understanding only 1 query should have been fired that is for fetching person data not for person detail data as i have mentioned
lazy loading. Why personDetail data is getting fetched along with person data ?
Hibernate cannot proxy your own object as it does for Sets / Lists in a #ToMany relation, so Lazy loading does not work.
I think this link could be useful to understand your problem: http://justonjava.blogspot.co.uk/2010/09/lazy-one-to-one-and-one-to-many.html
Based on your comment and since the PersonDetail entity contains a foreign key column that references the Person entity, it looks like you only have 1 problem:
Entity relationships include the concept of a relationship owner (in this case PersonDetail), which means that you want to add a #JoinColumn annotation in the PersonDetail entity.
You have already correctly defined the inverse side of the relationship (the entity that is not the owner of the relationship) with the mappedBy attribute that was added to the association annotation (#OneToOne in your case) to make the relationship bi-directional, which means that the associated PersonDetail may be reached from a Person instance.
Given the relationship that is clarified in your comment, you should only have to make 1 change to your code as shown here:
#Entity
public class Person {
#Id
#GeneratedValue
private int personId;
//Retain the mappedBy attribute here:
#OneToOne(cascade=CascadeType.ALL, mappedBy="person",
fetch=FetchType.LAZY)
private PersonDetail personDetail;
//getters and setters...
}
#Entity
public class PersonDetail {
#Id
#GeneratedValue
private int personDetailId;
#OneToOne(cascade=CascadeType.ALL, fetch=FetchType.LAZY)
//Change: add the #JoinColumn annotation here:
#JoinColumn(name="PERSON_FK_COLUMN")
private Person person;
//getters and setters...
}
I have searched and found similar issues, but they don't quite seem to be the same problem as
Why am I getting this NullPointer exception?
OneToOne Mapping with hibernate/JBoss/Seam
ANN-613 - NPE when mappedBy property is wrong on a #OneToOne
ANN-558 - #OneToMany(mappedBy="") can not recognize properties in parent classes
Hibernate Users - NPE with #Id on #OneToOne
I have a few entities mapped like this:
Person
|
+--User
I want to add a new entity PersonPartDeux with a OneToOne mapping to Person. The resulting mapping should look something like this:
Person + PersonPartDeux
|
+--User
When I do so, a NullPointerException is thrown while trying to load the mapping:
java.lang.NullPointerException
at org.hibernate.cfg.OneToOneSecondPass.doSecondPass(OneToOneSecondPass.java:135)
How do I specify the mapping so I can avoid this exception?
Here's my code:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class Person implements Serializable
{
#Id
#GeneratedValue
public Long id;
#Version
public int version = 0;
public String name;
#OneToOne(cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
public PersonPartDeux personPartDeux;
}
#Entity
public class PersonPartDeux implements Serializable
{
#Id
#GeneratedValue(generator = "person-primarykey")
#GenericGenerator(
name = "person-primarykey",
strategy = "foreign",
parameters = #Parameter(name = "property", value = "person")
)
public Long id = null;
#Version
public int version = 0;
#OneToOne(optional=false, mappedBy="person")
public Person person;
public String someText;
}
#Entity
#PrimaryKeyJoinColumn(name = "person_Id")
public class User extends Person
{
public String username;
public String password;
}
As for why I'm bothering, I need both the inheritance and the OneToOne mapping to solve different known issues in my application.
Attach the Hibernate source to your project, so you can click thru or 'Open Type' (Ctrl-Shift-T in Eclipse) to view the OneToOneSecondPass source.
Seeing the source, will give you a clear indication as to what needs to be specified.
In my source (Hibernate 4.1.7), line 135 is
propertyHolder.addProperty( prop, inferredData.getDeclaringClass() );
However you're probably using an earlier version.
Looking at the mappings, I'm suspicious of the #OneToOne definition -- mappedBy="person".
#OneToOne(optional=false, mappedBy="person")
public Person person;
What does it usefully mean, to map an association property by itself? Hibernate already knows the property is a OneToOne -- you just told it that.
Pointing the underpinning mapping/ FK of the property, at itself.. probably isn't actually telling Hibernate any correct or useful information.
Here's an example from the HB dosc, perhaps showing better how to do what you want:
#Entity
class MedicalHistory implements Serializable {
#Id Integer id;
#MapsId #OneToOne
#JoinColumn(name = "patient_id")
Person patient;
}
#Entity
class Person {
#Id #GeneratedValue Integer id;
}
Source: http://docs.jboss.org/hibernate/annotations/3.5/reference/en/html_single/
(3.5 docs off JBoss site.)
Cheers, hope this helps.