我如何使用 Spring Data Neo4j 创建一个与现有节点具有新关系的节点

How can I use Spring Data Neo4j to create a Node with new Relationships to existing Nodes

我的用例是我想通过在两个用户之间创建一个消息节点来将消息从一个用户发送到另一个用户。这是我想从客户端发送的 JSON 和我的消息 class:

{"text":"Hello!","recipient":{"userId":"1823498e-e491-45fa-95bb-782a937a00ba"},"sent":{"userId":"6467b976-7ff6-4817-98e0-f3fbfca1413e"}}
@Node("Message")
@Data
public class Message
{   
    @Id @GeneratedValue private UUID messageId; 
    
    @NotNull    
    private String text;
    
    @Relationship(type = "SENT",direction = Direction.INCOMING) 
    private User sent;
    
    @Relationship("TO") 
    private User recipient;
}

但是当我在 ReactiveNeo4jRepository 中调用 Mono<Message> save(Message message); 时,数据库唯一约束抱怨说具有这些用户名的用户已经存在!它似乎正在尝试将这两个 userId 的所有其他属性重置为 empty/defaults,大概是因为它们未包含在 JSON 中。所以当它看到两个“FAKE”用户名时,我得到一个约束错误。

2022-03-26 14:34:29.120 DEBUG 51863 --- [o4jDriverIO-2-4] o.n.d.i.a.o.OutboundMessageHandler       : [0xb08eba68][x.server.com:7687][bolt-128] C: RUN "MERGE (user:`User` {userId: $__id__}) SET user += $__properties__ RETURN user" {__id__="6467b976-7ff6-4817-98e0-f3fbfca1413e", __properties__={lastName: NULL, credentialsNonExpired: FALSE,userId: "6467b976-7ff6-4817-98e0-f3fbfca1413e", enabled: FALSE, firstName: NULL, password: "FAKE",accountNonExpired: FALSE, email: NULL,username: "FAKE", accountNonLocked: FALSE}} {bookmarks=["FB:kcwQx2Hl5+KYQJiGdiyzOa4EWSCQ"]}
2022-03-26 14:34:29.206 DEBUG 51863 --- [o4jDriverIO-2-4] o.n.d.i.a.i.InboundMessageDispatcher     : [0xb08eba68][x.server.com:7687][bolt-128] S: SUCCESS {fields=["user"], t_first=2}
2022-03-26 14:34:29.207 DEBUG 51863 --- [o4jDriverIO-2-4] o.n.d.i.a.o.OutboundMessageHandler       : [0xb08eba68][x.server.com:7687][bolt-128] C: PULL {n=-1}
2022-03-26 14:34:29.301 DEBUG 51863 --- [o4jDriverIO-2-4] o.n.d.i.a.i.InboundMessageDispatcher     : [0xb08eba68][x.server.com:7687][bolt-128] S: FAILURE Neo.ClientError.Schema.ConstraintValidationFailed "Node(1) already exists with label `User` and property `username` = 'FAKE'"

更新:我可以直接使用 Jackson 的 ObjectMapper 和 Neo4j 驱动程序完成我需要的工作,如下所示。但我仍然想知道如何使用 SDN。

    @Override
    public Mono<UUID> save(Message message) 
    {
        String cypher = "MATCH (a:User),(b:User) where a.userId = $fromUserId and b.userId = $toUserId CREATE (a)-[:SENT]->(m:Message {messageId: $messageId, text : $text})-[:TO]->(b)";
        Map<String,Object> objMap = persistenceObjectMapper.convertValue(message,mapType);
        return Mono.from(driver.rxSession().writeTransaction(tx -> tx.run(cypher,objMap).records())).then(Mono.just(message.getMessageId()));
    }

It seems like it's trying to reset all of the other properties for those two userIds to empty/defaults

您面临的问题根源在于对传入 Message 对象的处理。 据我了解,您是直接保存带有附件的 Message de-serialized Users.

您必须先从数据库中获取 User。 SDN 没有 pre-fetch。一种方法是为 User 实体创建一个额外的存储库。

给定一个被控制器调用的 MovieService,你最终会得到这样的结果:

@Transactional
public Message saveNewMessage(Message newMessage) {
    UUID recipientId = newMessage.getRecipient().getUserId();
    UUID senderId = newMessage.getSent().getUserId();

    User recipient = userRepository.findById(recipientId).orElseGet(() -> {
        User user = new User();
        user.setName("new recipient");
        return user;
    });

    User sender = userRepository.findById(senderId).orElseGet(() -> {
        User user = new User();
        user.setName("new sender");
        return user;
    });

    newMessage.setRecipient(recipient);
    newMessage.setSent(sender);

    return messageRepository.save(newMessage);
}

用以下方法测试过:

CREATE CONSTRAINT username ON (user:User) ASSERT user.name IS UNIQUE

4.3 and/or 4.4

CREATE CONSTRAINT username FOR (user:User) REQUIRE user.name IS UNIQUE

我为您的用例创建了一个示例:https://github.com/meistermeier/neo4j-issues-examples/tree/master/so-71594275 我还有一个用户属性擦除调用堆栈 (/clearusers),它显示了我认为在您的应用程序中发生的情况。

编辑:由于我通常建议不要为每个实体创建存储库 class,您也可以使用 Neo4jTemplate。我已将 saveNewMessageAlternative 添加到示例中。