troig
troig

Reputation: 7212

spring-data-neo4j basic one-to-many relationship not persisting

EDIT: Sample project available on github.

I'm using Neo4J (Rest graph database, hosted in grapheneDb) and Spring Data in our backend project.

<bean id="graphDatabaseService" class="org.springframework.data.neo4j.rest.SpringCypherRestGraphDatabase">

I have a simple one-to-many relationship between two entities: User and Stay.

EDIT: I thought this wasn't relevant for the issue, but after seeing a similar problem in SDN4, I think I need to update the question (there is a basic @NodeEntity class, and both entities are extending this base class).

@NodeEntity
public abstract class BasicNodeEntity implements Serializable {

   @GraphId
   private Long nodeId;
}


public class User extends BasicNodeEntity {

  @RelatedTo(type = "HAS_STAY",  direction = Direction.OUTGOING)
  Set<Stay> stays;

  public void addStay(Stay stay) {
     stays.add(stay);
  }
}

public class Stay extends BasicNodeEntity {

   @RelatedTo(type = "HAS_STAY", direction = Direction.INCOMING)
   User user;
}

I'm unable to persist more than one stay. The first stay I add to the user is persisted correctly, but just the first one. The next stays added never persists, and I always retrieve the first one.

The method I use to create a new stay is:

   @Autowired
   Neo4jOperations template;

   @Transactional
   private void createStay(Stay stay, User user) throws Exception {
      stay = template.save(stay);
      user.addStay(stay);
      template.save(user);
      // If i evaluate user at this point, it contains both stays

      // But if I retrieve the user from the repository, it just contains
      // the first stay, the second one has not persisted.
   }

EDIT: User modified is retrieved correctly through UserRepository.

public interface UserRepositoryCustom {}

public interface UserRepository extends GraphRepository<User>, UserRepositoryCustom {    
   User findById(String id);
}

User user = userRepository.findById(userId);

NOTE: I also tried to save through the repository interface instead of the Neo4jTemplate one, but I have the same problem.

Both entities are correctly saved in the neo4j database, it's just a persistence issue.

I think this should be quite easy, so I'm probably missing something..

Any help would be greatly appreciated.

Relevant versions:

<spring.version>4.0.5.RELEASE</spring.version>
<spring-data-neo4j.version>3.3.2.RELEASE</spring-data-neo4j.version>

There is another SO question with a very similar problem, but without response so far.

Upvotes: 4

Views: 1625

Answers (1)

Michael Hunger
Michael Hunger

Reputation: 41676

It is a tricky thing.

Your custom equals method causes two entities which have their node-id set but not yet their uuid-id set, to be equal so that when loading them into a set the set will only contain one.

Code in: RelationshipHelper

protected Set<Object> createEntitySetFromRelationshipEndNodes(Object entity, final MappingPolicy mappingPolicy, final Class<?> relatedType) {
    final Iterable<Node> nodes = getStatesFromEntity(entity);
    final Set<Object> result = new HashSet<Object>();
    for (final Node otherNode : nodes) {
        Object target = template.createEntityFromState(otherNode, relatedType, mappingPolicy);
        result.add(target);
    }
    return result;
}

If you change your code to have an equals/hashcode in your BasicNode entity:

   @Override
   public boolean equals(Object o) {
      if (this == o) return true;
      if (!(o instanceof BasicNodeEntity)) return false;

      BasicNodeEntity that = (BasicNodeEntity) o;

      if (nodeId != null) {
         if (!nodeId.equals(that.nodeId)) return false;
      } else {
         if (that.nodeId != null) return false;
      }

      return true;
   }

   @Override
   public int hashCode() {
      return nodeId != null ? nodeId.hashCode() : 0;
   }

so that entities that have only a nodeId set are comparable

and adapt the subclass methods

   @Override
   public boolean equals(Object o) {
      if (this == o) return true;
      if (!(o instanceof IdentifiableEntity)) return false;

      IdentifiableEntity entity = (IdentifiableEntity) o;
      //change
      if (!super.equals(o)) return false;

      if (id != null) {
         if (!id.equals(entity.id)) return false;
      } else {
         if (entity.id != null) return false;
      }

      return true;
   }

   @Override
   public int hashCode() {
      //change
      if (super.hashCode() != 0) return super.hashCode();
      return id != null ? id.hashCode() : 0;
   }

Then it works.

Going forward if you are working with Neo4j Server I recommend to you to check out SDN 4 RC2 instead which was released on Friday.

Upvotes: 2

Related Questions