Problem with saving new relationships - StackOverflow exception

Hi, I'm creating a spring-boot app with Neo4j as a database. Working on Java 11, and Neo4j community edition 4.2.1. I'm facing an issue with creating new relationships, which are either not saved, or StackOverflowException is thrown.
Firstly, I have two classes, which represents nodes:

@Node("Person")
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
public class Person implements UserDetails {
    @Id
    @GeneratedValue
    private Long id;

    private String firstName;

    private String lastName;

    private String email;

    private String password;

    private PersonRole personRole;

    @Relationship(type = "CREATED", direction = Relationship.Direction.OUTGOING)
    private List<Event> ownedEvents;

    @Relationship(type = "TAKES_PART", direction = Relationship.Direction.OUTGOING)
    private Set<Event> participatedEvents;

    @Relationship(type = "UPVOTED", direction = Relationship.Direction.OUTGOING)
    private Set<Event> upVotes;

    @Relationship(type = "DOWNVOTED", direction = Relationship.Direction.OUTGOING)
    private Set<Event> downVotes;

// Here are constructors, and other required methods, which are not very important here

Another class:

@Node("event")
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
@EqualsAndHashCode
public class Event {
    @Id
    @GeneratedValue
    Long id;
    private String title;
    private String description;

    @Relationship(type = "TAKES_PART", direction = Relationship.Direction.INCOMING)
    private Set<Person> participants;

    @Relationship(type = "HAS", direction = Relationship.Direction.OUTGOING)
    private Set<Link> links;
    
    public Event(String title, String description) {
        this.title = title;
        this.description = description;
    }
}

As you can see, I have a Person class which represents a real person, and some kind of event. A person can attend an event - so has a private field of type List participatedEvents annotated with outgoing relationship called "TAKES_PART". So, from a person node, we might have a relationship to an Event node. I guess it's clear up to now.

Next, in the Event class, there's also the private field of List type named participants, which stands for people, who will attend a particular event (from an event point of view). It is annotated with @Relationship INCOMING - I hope I understand it correctly, how this could work.

The problem occurs while adding a person to the event. Here's the code:

    public void addPersonToEvent(Long eventId, Long personId) throws UserNotExistsException, EventNotFoundException, EventNotOwnedException {
        this.checkEventOwnership(eventId);
        Event event = this.eventRepository.findById(eventId).orElseThrow(EventNotFoundException::new);
        Person p = this.personRepository.findById(personId).orElseThrow(UserNotExistsException::new);

        Set<Person> participants = event.getParticipants();
        Set<Event> participated = p.getParticipatedEvents();

        if (participated == null)
            participated = new HashSet<>();

        if (participants == null) {
            participants = new HashSet<>();
        }

        participated.add(event);
        p.setParticipatedEvents(participated);
        this.personRepository.save(p);
        participants.add(p);
        event.setParticipants(participants);
        this.eventRepository.save(event);
    }

I get selected by Id event from the database, and a given by Id person. I will need them both, to create a new relationship. Next, I check whether any of the lists are empty - nothing interesting here. After that, I'm assigning a person to the event in List from event object, and similarly assigning the event in a person List. At last, I'm using the repository method Save, to affect the database. Here's the code for the repository:

@Repository
public interface EventRepository extends Neo4jRepository<Event, Long> {

}

The problem occurs, when saving the event - TransactionManager makes a rollback, and StackOverflow exception is thrown - it looks like some kind of circular dependency issue. I've tried, to remove assigning the event in Person List, but then, the relationship is not created. An exception is not thrown though. I've already got to know, that I should affect both Lists, because in the case of affecting just one, inconsistency appears, and the DB manager does not know, which state is correct - so nothing happens.

Could you give me any hint, how to resolve that issue, or how this operation should be done in a proper way? To my mind, it might be a very simple solution, or a bug fix in my code, which I can't notice.

Here is also command line output:

2021-05-25 09:51:54.777 DEBUG 139951 --- [nio-8080-exec-2] o.s.d.n.c.t.Neo4jTransactionManager      : Initiating transaction commit
2021-05-25 09:51:54.781 DEBUG 139951 --- [nio-8080-exec-2] o.s.d.n.c.t.Neo4jTransactionManager      : Creating new transaction with name [org.springframework.data.neo4j.repository.support.SimpleNeo4jRepository.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2021-05-25 09:51:54.786 DEBUG 139951 --- [nio-8080-exec-2] org.springframework.data.neo4j.cypher    : Executing:
OPTIONAL MATCH (hlp:`event`) WHERE id(hlp) = $__id__ WITH hlp WHERE hlp IS NULL CREATE (n:`event`) SET n = $__properties__ RETURN id(n) UNION MATCH (n:`event`) WHERE id(n) = $__id__ SET n += $__properties__ RETURN id(n)
2021-05-25 09:51:54.802 DEBUG 139951 --- [nio-8080-exec-2] o.s.d.n.c.t.Neo4jTransactionManager      : Initiating transaction rollback
2021-05-25 09:51:54.819 ERROR 139951 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.StackOverflowError] with root cause

java.lang.StackOverflowError: null
	at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:120) ~[na:na]
	at com.hm.zti.fis.musicpal.event.Event.hashCode(Event.java:20) ~[classes/:na]
	at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
	at com.hm.zti.fis.musicpal.person.Person.hashCode(Person.java:23) ~[classes/:na]
	at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
	at com.hm.zti.fis.musicpal.event.Event.hashCode(Event.java:20) ~[classes/:na]
	at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
	at com.hm.zti.fis.musicpal.person.Person.hashCode(Person.java:23) ~[classes/:na]
	at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
	at com.hm.zti.fis.musicpal.event.Event.hashCode(Event.java:20) ~[classes/:na]
	at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
	at com.hm.zti.fis.musicpal.person.Person.hashCode(Person.java:23) ~[classes/:na]
	at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
	at com.hm.zti.fis.musicpal.event.Event.hashCode(Event.java:20) ~[classes/:na]
	at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
	at com.hm.zti.fis.musicpal.person.Person.hashCode(Person.java:23) ~[classes/:na]
	at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
.
.
.

Thanks for your time!

I would blame Lombok for this..seems like Event's hashCode points to Person's hashCode which points to Event's hashCode pointing towards...

1 Like

Your suspicion is totally correct. After removing Lombok annotation @EqualsAndHashCode, everything works just fine. Relationships are created with good direction, and also they can be removed without any side effects.

Thank you so much! I can move on with my project now.
Good day to you! :wink: