JPA : How to create a self-join entity? - java

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?

Related

How to solve circular reference in JPA associations?

I know this kind of question was answered many times and there are solutions to it, however, none of them worked for me. I tried #JsonIgnore, #JsonIgnoreProperties #JsonManagedReference/#JsonBackedReference, yet still the debugger shows that user has reference to authority, which has reference to user, which has reference to authority, which has reference to user ... Most importantly it doesn't throw any exceptions. However, I still wonder why does this happen, why it doesn't throw exceptions, and does it affect productivity
My entities are simple: there is a User
#Entity
#Table(name = "users_tb")
#NoArgsConstructor
#Getter
#Setter
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Authority> authorities;
}
and Authority
#Entity
#Table(name = "authorities_tb")
#NoArgsConstructor
#Getter
#Setter
public class Authority {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
}
the code to retrieve users using JpaRepository<T, V>
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
var user = userRepository.findUserByUsername(username).orElseThrow(
() -> new UsernameNotFoundException(ERR_USERNAME_NOT_FOUND));
return new CustomUserDetails(user);
}
The debugger output state before return from loadUserByUsername:
user = {User#10781}
> id = {Long#10784}
> username = "John"
> password = "$2a$10$xn3LI/AjqicFYZFruSwve.681477XaVNaUQbr1gioaWPn4t1KsnmG"
> authorities = {PersistentBag#10788} size = 2
> 0 = {Authority#10818}
> id = {Long#10784}
> name = "READ"
> user = {User#10784}
> id = {User#10784}
> username = "John"
> password = "$2a$10$xn3LI/AjqicFYZFruSwve.681477XaVNaUQbr1gioaWPn4t1KsnmG"
> authorities = {PersistentBag#10788} size = 2
> 0 = {Authority#10818}
...
Circular dependencies aren't a problem in themselves with JPA.
There are two potential problems with them:
From a software design perspective circular dependencies create a cluster of classes that you can't easily break up.
You can easily get rid of them in your case by making the relationship a unidirectional one and replace the other direction by a query, if you really have to.
Is it worth it in your case?
It depends how closely your two entities are really related.
I'd try to avoid bidirectional relationships, because it is easy to make mistakes, like not keeping both sides of the relationship in sync.
But in most cases I wouldn't sweat it.
Most software as way more serious design issues.
The other problem occurs when something tries to navigate this loop until its end, which obviously doesn't work. The typical scenarios are:
rendering it into JSON (or XML). This is what #JsonIgnore & Co takes care of by not including properties in the JSON.
equals, hashCode, toString are often implemented to call the respective methods of all referenced objects.
Just as the JSON rendering this will lead to stack overflows.
So make sure to break the cycle in these methods as well.
JPA itself doesn't have a problem with cycles because it will look up entities in the first level cache.
Assuming you load an Authority and everything is eagerly loaded, JPA will put it in the first level cache, before checking the referenced user id. If it is present in the cache it will use that instance.
If not it will load it from the database, put it in the cache and then check for the authorities ids in the cache. It will use the ones found and load the rest.
For those it will again check the user id, but those are the user we just loaded, so it is certainly in the cache.
Therefore JPA is done and won't get lost in a cycle.
It will just skip the annotated
Try not to use the Lombok annotation #Getter and #Setter. Then generate manually getters and setters and use #JsonIgnore on the class member field and the getter, and #JsonProperty on the setter.
#JsonIgnore
private List<Authority> authorities;
#JsonIgnore
// Getter for authorities
#JsonProperty
// Setter for authorities
You can simply annotate the duplicated field with #ToString.Exclude
In you case:
#Data // this includes getter/setter and toString
#Entity
#Table(name = "users_tb")
#NoArgsConstructor
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
#ToString.Exclude
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Authority> authorities;
}
#Data
#Entity
#Table(name = "authorities_tb")
#NoArgsConstructor
public class Authority {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ToString.Exclude
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
}
More info: Lombok #Data and #ToString.Exclude

Infinite recursion with Jackson on intermediate table

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.

Spring MVC + Hibernate & Jackson = Could not write JSON: Infinite recursion

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.

JPA OneToMany insert not setting the id's correctly

I have two entities, let's say
Person.java:
#Entity
public class Person implements Serializable {
#Id
#GeneratedValue(strategy = AUTO)
private long id;
#OneToMany(mappedBy = "personData", cascade = CascadeType.PERSIST)
private List<SkillsData> skillsData;
// ...
}
SkillsData.java
#Entity
#Table(name = "SkillsData")
public class SkillsData implements Serializable {
#Id
#GeneratedValue(strategy = AUTO)
private long id;
#JoinColumn(name = "PERSONID")
#ManyToOne(cascade = REMOVE)
private Person personData;
// ...
}
When I create a person, add a list of type SkillsData to it's skillsData field and persist it everything works with no exceptions thrown, but when I browse the database directly in the SkillsData table the field PERSONID is not populated and because of that the skills added can't be referenced to the right person.
I'm trying to fix this problem for quite some time and I'll be thankful for any help.
The problem might be in the fact that you're not setting SkillsData.personData before persisting leaving it null.
You must set it cause adding SkillsData to the Person.skillsData list is not enough since you declared this side of relationship as inverse(mappedBy attribute).
Therefore it is the SkillsData.personData non-inverse side who is responsible for establishing this relationship.

How do I avoid NullPointerException with OneToOne mapping against a parent entity class?

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.

Categories