0
votes

I have a User node with FRIEND_REQUEST relationships mapped to a sentFriendRequestList list and to a receivedFriendRequestList list like below:

@Node
data class User(

    @Id
    @GeneratedValue(UUIDStringGenerator::class)
    var userId: String?,

    @Relationship(type = "FRIEND_REQUEST", direction = Relationship.Direction.OUTGOING)
    var sentFriendRequestList: MutableList<FriendRequest> = mutableListOf(),

    @Relationship(type = "FRIEND_REQUEST", direction = Relationship.Direction.INCOMING)
    var receivedFriendRequestList: MutableList<FriendRequest> = mutableListOf(),

    var email: String

)

The FriendRequest class:

@RelationshipProperties
data class FriendRequest(

    @Id
    @GeneratedValue
    var friendRequestId: Long?,

    /**
     * Represents the receiver in an OUTGOING relationship and the sender in an INCOMING relationship.
     */
    @TargetNode
    var friendRequestOtherNode: User

){
    constructor(friendRequestOtherNode: User) : this(null, friendRequestOtherNode)
}

When saving multiple friend requests, on some occasions all previously created relationships disappear from the given nodes and only the newly created relationship appears.

I save like this:

fun saveFriendRequest(sender: User, receiver: User) {
        val sentFriendRequest = FriendRequest(receiver)
        val receivedFriendRequest = FriendRequest(sender)
        sender.sentFriendRequestList.add(sentFriendRequest)
        receiver.receivedFriendRequestList.add(receivedFriendRequest)
        userRepository.save(sender)
        userRepository.save(receiver)
    }

I don't understand what the problem is, especially since sometimes it runs without failure.

I created a small test project which is available on my GitHub: link. It contains the data structure and a test class that can be run instantly. The tests show the same problem, after multiple runs it can either fail or be successful.

1

1 Answers

0
votes

I am a little bit unsure about the right query, but your custom Cypher statement does not return all friends and relationships for deeper links. Changing it to:

MATCH (user:User{email: \$email})
OPTIONAL MATCH path=(user)-[:FRIEND_REQUEST*]-(friend:User)
WITH user, friend, relationships(path) as friend_requests
RETURN user, collect(friend_requests), collect(distinct(friend))

solved this for me (or the ten test runs were just randomly green). Another solution would be, to avoid the custom query, define findByEmail(email: String): User and let Spring Data Neo4j create the query.

The problem that occurs is that

First operation:
frodo -> sam
sam -> frodo

Second operation:
frodo -> bilbo
bilbo -> frodo

results for Sam in something like sam-frodo-bilbo. When you load Frodo or Bilbo the relationships are not completely hydrated to Sam. The moment you save Bilbo (or Frodo), SDN will through all relationship and eventually not find the Sam relation. Because it is empty, SDN will remove the relationship on save.

Another problem I have seen in your code: You should definitely annotate the manual defined constructor with @PersistenceConstructor because Kotlin creates an invisible copy constructor and Spring Data in general does not know which to choose. So you might randomly run into the copy constructor.