2

I have an entity as below. I am curious if it is possible to create a relationship as I will be describing with the example:

  • I am creating 2 Person entities Michael and Julia.
  • I am adding Julia to Michael's friends set.
  • After that I am retrieving Michael as a JSON response and Julia is available in the response. But when I am retrieving Julia, her friends set is empty. I want to create the bidirectional friendship relation by saving just one side of the friendship. I would like to get Michael on Julia's friends set without doing any other operations. I think that it must be managed by Hibernate. Is it possible and how should I do it?

    @ToString(exclude = "friends") // EDIT: these 2 exclusion necessary
    @EqualsAndHashCode(exclude = "friends")
    public class Person{
    
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;
    
    @Column(name = "name",unique = true)
    private String name;
    
    @JsonIgnoreProperties("friends") // EDIT: will prevent the infinite recursion
    @ManyToMany(cascade = CascadeType.ALL)
    @JoinTable(name = "FRIENDSHIP",
           joinColumns = @JoinColumn(name = "person_id", 
    referencedColumnName = "id"),
           inverseJoinColumns = @JoinColumn(name = "friend_id", 
    referencedColumnName = "id"))
    private Set<Person> friends;
    

Here is my service layer code for creating a friendship:

    @Override
    public Person addFriend(String personName, String friendName) 
        throws FriendshipExistsException, PersonNotFoundException {
    Person person = retrieveWithName(personName);
    Person friend = retrieveWithName(friendName);
    if(!person.getFriends().contains(friend)){
        person.getFriends().add(friend);
        return repository.save(person);
    }
    else{
        throw new FriendshipExistsException(personName, friendName);
    }
}

Related Question: N+1 query on bidirectional many to many for same entity type

Updated the source code and this version is working properly.

el nino
  • 129
  • 1
  • 3
  • 10
  • 1
    You need to setup bidirectional relationship manually. Like `friend.addFriend(person) and person.addFriend(friend)`. If you want to handle such kind of relations, consider a graph database instead. I prefer to create a helper method inside my entity like `makeFriendship(Person person)` and setup the relations there to make the code more readable. – Bogdan Oros Jan 31 '18 at 21:14
  • @BogdanOros I will be updating as this way but I am not sure that I should make it manually by adding to each other's friends list. – el nino Jan 31 '18 at 21:18
  • [Official documentation](https://docs.jboss.org/hibernate/orm/4.1/manual/en-US/html/ch07.html) shows that it is how it works. It is done to support relations in database, because in a relation database you need to have two rows to specify this relation. – Bogdan Oros Jan 31 '18 at 21:21
  • @BogdanOros I am aware of it and exactly asking if this is possible. I want to insert two rows in friendship table with just one save operation. – el nino Jan 31 '18 at 21:27
  • You will call the `save` operation ones, because you have configured cascades and it will automatically persist the child set with updated friendship relationship. – Bogdan Oros Jan 31 '18 at 21:29

1 Answers1

1
// Creating a graph to help hibernate to create a query with outer join.
@NamedEntityGraph(name="graph.Person.friends",
    attributeNodes = @NamedAttributeNode(value = "friends"))
class Person {}

interface PersonRepository extends JpaRepository<Person, Long> {
    // using the named graph, it will fetch all friends in same query
    @Override
    @EntityGraph(value="graph.Person.friends")
    Person findOne(Long id);
}

@Override
public Person addFriend(String personName, String friendName) 
    throws FriendshipExistsException, PersonNotFoundException {
    Person person = retrieveWithName(personName);
    Person friend = retrieveWithName(friendName);
    if(!person.getFriends().contains(friend)){
        person.getFriends().add(friend);
        friend.getFriends().add(person); // need to setup the relation
        return repository.save(person); // only one save method is used, it saves friends with cascade
    } else {
        throw new FriendshipExistsException(personName, friendName);
    }

}

If you check your hibernate logs, you will see:
Hibernate: insert into person (name, id) values (?, ?)
Hibernate: insert into person (name, id) values (?, ?)
Hibernate: insert into friendship (person_id, friend_id) values (?, ?)
Hibernate: insert into friendship (person_id, friend_id) values (?, ?)

Bogdan Oros
  • 1,249
  • 9
  • 13
  • It is exactly giving the stackoverflow error on the line: `friend.getFriends().add(person);` – el nino Feb 01 '18 at 05:54
  • Tried same example , no stackoverflow error, could you paste stackstrace? – Bogdan Oros Feb 01 '18 at 08:25
  • I just solved the stackoverflow error. Lombok's toString(), equals() and hashCode() methods were causing it. I excluded the friends element than it is solved. Now I have a problem while getting a person. For example when I get Julia, Michael is on the friend list and it is trying to get Michael's friend list and it is bringing Julia an it goes on as an infinite loop. It is N+1 query problem and I am trying to solve it. – el nino Feb 01 '18 at 08:40
  • Yes, Lombok cannot resolve backward references, that's why I moved to Kotlin with dataclasses etc. – Bogdan Oros Feb 01 '18 at 08:42
  • Do you have any solution advice for the problem I mentioned up there? – el nino Feb 01 '18 at 08:44
  • Trying to configure `NamedEntityGraph` to setup correct fetching – Bogdan Oros Feb 01 '18 at 08:53
  • I tried this solution but it is still giving this error. Failed to write HTTP message: org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion – el nino Feb 01 '18 at 09:09
  • check [Tutorial](http://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion) how to handle recursion problems with json mappers – Bogdan Oros Feb 01 '18 at 09:11
  • I had tried it before but it is not about Jackson. I want to get just friends of the root person. Hibernate is bringing it with N+1 query. I found that [Tutorial](https://martinsdeveloperworld.wordpress.com/2014/07/02/using-namedentitygraph-to-load-jpa-entities-more-selectively-in-n1-scenarios/). – el nino Feb 01 '18 at 09:14
  • I asked a related question about the infinite recursion: https://stackoverflow.com/questions/48562273/n1-query-on-bidirectional-many-to-many-for-same-entity-type – el nino Feb 01 '18 at 12:01