You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Neo4j Spring同类型实体双向KNOWS关系建模及栈溢出问题咨询

Why You're Getting StackOverflowError & Better Modeling for Mutual KNOWS Relationships

Let's break down what's wrong with your current setup and fix it step by step.

What's Wrong With Your Current Modeling?

Your approach is causing a circular reference loop that triggers a StackOverflowError during persistence:

  • When you save user2, Spring Data Neo4j (SDN) processes its outgoing knows set, which references user1.
  • SDN then processes user1's incoming knownBy set, which references user2.
  • This back-and-forth recursion repeats infinitely until the call stack is exhausted.

Additionally, maintaining separate knows (outgoing) and knownBy (incoming) collections with @RelationshipProperties is unnecessary for most use cases—Neo4j relationships are inherently traversable in both directions without needing duplicate physical relationships.

Better Implementation Options

If you just need to traverse the relationship in both directions (not create two separate physical relationships), you only need to maintain one direction of the relationship in your entity. Neo4j lets you query relationships regardless of their defined direction.

Updated Entity Code

@Node
public class User {
    @Id @GeneratedValue 
    private UUID id;
    private String name;
    private String phoneNumber;
    private String email;
    private boolean isActive;

    // Only maintain outgoing KNOWS relationships with properties
    @JsonIgnore
    @Relationship(type = "KNOWS", direction = Relationship.Direction.OUTGOING)
    private Set<Knows> knows = new HashSet<>();

    public User() { }

    // Helper to add a KNOWS relationship
    public void addKnows(User targetUser, String relationshipLabel) {
        Knows relationship = new Knows(relationshipLabel, Instant.now(), targetUser);
        this.knows.add(relationship);
    }

    // Getters/setters for fields (omitted for brevity)
}

@RelationshipProperties
public class Knows {
    private String as;
    @CreatedDate 
    private Instant createdAt;
    @JsonIgnore
    @TargetNode 
    private User targetUser;

    public Knows(String as, Instant createdAt, User targetUser) {
        this.as = as;
        this.createdAt = createdAt;
        this.targetUser = targetUser;
    }

    // Getters/setters (omitted for brevity)
}

Saving the Relationship

User user1 = userRepository.findById(id1).orElseThrow();
User user2 = userRepository.findById(id2).orElseThrow();

// Create a single outgoing relationship from user2 to user1
user2.addKnows(user1, "friend");
userRepository.save(user2);

Querying the Reverse Relationship

To find who knows user1, use a custom repository query instead of maintaining an incoming collection:

@Repository
public interface UserRepository extends Neo4jRepository<User, UUID> {
    @Query("MATCH (knownUser:User)<-[:KNOWS]-(user:User) WHERE knownUser.id = $userId RETURN user, KNOWS")
    List<User> findUsersWhoKnow(@Param("userId") UUID userId);
}

Option 2: Physical Mutual Relationships (Two Separate Relationships)

If you truly need two distinct physical relationships (matching your original Cypher), avoid using entity collections to manage both directions—use a direct Cypher query instead to bypass the circular reference issue.

Custom Repository Method

@Repository
public interface UserRepository extends Neo4jRepository<User, UUID> {
    @Query("MATCH (u1:User {id: $id1}), (u2:User {id: $id2}) " +
           "CREATE (u1)-[:KNOWS {as: $as, createdAt: $createdAt}]->(u2) " +
           "CREATE (u2)-[:KNOWS {as: $as, createdAt: $createdAt}]->(u1)")
    void createMutualKnowsRelationship(
        @Param("id1") UUID id1, 
        @Param("id2") UUID id2, 
        @Param("as") String as, 
        @Param("createdAt") Instant createdAt
    );
}

Executing the Query

userRepository.createMutualKnowsRelationship(id1, id2, "friend", Instant.now());

Key Takeaways

  • Avoid circular collections: Never maintain both incoming and outgoing relationship collections in entities when using @RelationshipProperties—it triggers infinite recursion during persistence.
  • Leverage Neo4j's flexibility: Most "bidirectional" relationship needs can be solved with a single physical relationship and bidirectional queries, no duplicate relationships required.
  • Use direct Cypher for edge cases: When you need duplicate physical relationships, bypass entity mapping and execute Cypher directly via repository methods.

内容的提问来源于stack exchange,提问作者Vimal23

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 07:43:06