2016-09-08 6 views
0

Um einen Baum/Hierarchie zu modellieren (wo die Eltern-Kind-Beziehung in beiden Richtungen durchlaufen werden kann) mit Spring Data Neo4j 4.1, schrieb ich die folgenden EntitätsklasseModellierung Baum/Hierarchiestruktur in Spring Data Neo4j 4.1

@NodeEntity(label = "node") 
public class Node { 

    @GraphId 
    @SuppressWarnings("unused") 
    private Long graphId; 

    private String name; 

    @Relationship(type = "PARENT", direction = Relationship.OUTGOING) 
    private Node parent; 

    @Relationship(type = "PARENT", direction = Relationship.INCOMING) 
    private Iterable<Node> children; 

    @SuppressWarnings("unused") 
    protected Node() { 
     // For SDN. 
    } 

    public Node(String name, Node parent) { 
     this.name = Objects.requireNonNull(name); 
     this.parent = parent; 
    } 

    public String getName() { 
     return name; 
    } 

    public Node getParent() { 
     return parent; 
    } 
} 

Das Problem ist, dass offenbar das Vorhandensein der children Feld verschraubt die PARENT Beziehung, so dass es nur eine solche eingehende Beziehung für einen Knoten geben kann. Das heißt, wie durch den folgenden Testfall gezeigt, kann ein Knoten nicht mehr als ein Kind hat - „widersprüchlichen“ Beziehungen werden automatisch gelöscht:

@RunWith(SpringRunner.class) 
@SpringBootTest(
     classes = GraphDomainTestConfig.class, 
     webEnvironment = SpringBootTest.WebEnvironment.NONE 
) 
@SuppressWarnings("SpringJavaAutowiredMembersInspection") 
public class NodeTest { 

    @Autowired 
    private NodeRepository repository; 

    @Test 
    public void test() { 
     // Breakpoint 0 

     Node A = new Node("A", null); 

     A = repository.save(A); 
     // Breakpoint 1 

     Node B = new Node("B", A); 
     Node C = new Node("C", A); 

     B = repository.save(B); 
     // Breakpoint 2 

     C = repository.save(C); 
     // Breakpoint 3 

     A = repository.findByName("A"); 
     B = repository.findByName("B"); 
     C = repository.findByName("C"); 
     // Breakpoint 4 

     assertNull(A.getParent()); // OK 
     assertEquals(B.getParent().getName(), "A"); // FAILS (null pointer exception)! 
     assertEquals(C.getParent().getName(), "A"); // OK 
    } 
} 

Der Test wird eingerichtet, um die Embedded-Treiber zu verwenden. Die Protokollausgabe an den "Breakpoints" lautet wie folgt:

Um das Rauschen niedrig zu halten, habe ich mich darauf beschränkt, die Protokollausgabe einzuschließen, von der ich denke, dass sie mit dem Problem zusammenhängen könnte. Bitte fragen Sie nach mehr Ausgabe in den Kommentaren, wenn Sie es brauchen. Dasselbe gilt für die Konfiguration usw.

Haltepunkt 0: Merkwürdige Warnung.

WARN: No identity field found for class of type: com.example.NodeTest when creating persistent property for field: private com.example.NodeRepository com.example.NodeTest.repository 

Breakpoint- 1: Knoten A angelegt.

INFO: Request: UNWIND {rows} as row CREATE (n:`node`) SET n=row.props RETURN row.nodeRef as ref, ID(n) as id, row.type as type with params {rows=[{nodeRef=-1965998569, type=node, props={name=A}}]} 

Breakpoint- 2: Node B und seine Beziehung zu A erzeugt.

INFO: Request: UNWIND {rows} as row CREATE (n:`node`) SET n=row.props RETURN row.nodeRef as ref, ID(n) as id, row.type as type with params {rows=[{nodeRef=-1715570484, type=node, props={name=B}}]} 
INFO: Request: UNWIND {rows} as row MATCH (startNode) WHERE ID(startNode) = row.startNodeId MATCH (endNode) WHERE ID(endNode) = row.endNodeId MERGE (startNode)-[rel:`PARENT`]->(endNode) RETURN row.relRef as ref, ID(rel) as id, row.type as type with params {rows=[{startNodeId=1, relRef=-1978848273, type=rel, endNodeId=0}]} 

Breakpoint- 3: Knoten C und einen seine Beziehung wird erstellt. Aber Bs Beziehung zu A wird ebenfalls gelöscht!

INFO: Request: UNWIND {rows} as row CREATE (n:`node`) SET n=row.props RETURN row.nodeRef as ref, ID(n) as id, row.type as type with params {rows=[{nodeRef=-215596349, type=node, props={name=C}}]} 
INFO: Request: UNWIND {rows} as row MATCH (startNode) WHERE ID(startNode) = row.startNodeId MATCH (endNode) WHERE ID(endNode) = row.endNodeId MERGE (startNode)-[rel:`PARENT`]->(endNode) RETURN row.relRef as ref, ID(rel) as id, row.type as type with params {rows=[{startNodeId=2, relRef=-2003500348, type=rel, endNodeId=0}]} 
INFO: Request: UNWIND {rows} as row MATCH (startNode) WHERE ID(startNode) = row.startNodeId MATCH (endNode) WHERE ID(endNode) = row.endNodeId MATCH (startNode)-[rel:`PARENT`]->(endNode) DELETE rel with params {rows=[{startNodeId=1, endNodeId=0}]} 

Breakpoint- 4: das Repository abfragt.

INFO: Request: MATCH (n:`node`) WHERE n.`name` = { `name` } WITH n MATCH p=(n)-[*0..1]-(m) RETURN p, ID(n) with params {name=A} 
WARN: Cannot map iterable of class com.example.Node to instance of com.example.Node. More than one potential matching field found. 
INFO: Request: MATCH (n:`node`) WHERE n.`name` = { `name` } WITH n MATCH p=(n)-[*0..1]-(m) RETURN p, ID(n) with params {name=B} 
INFO: Request: MATCH (n:`node`) WHERE n.`name` = { `name` } WITH n MATCH p=(n)-[*0..1]-(m) RETURN p, ID(n) with params {name=C} 

Ich vermute, dass das Problem mit der Warnung in der zweiten Leitung verbunden wird (von „Breakpoint 4“), aber ich verstehe nicht, den Grund/Lösung für sie.

Warum kann das Feld nicht zugeordnet werden? Warum verursacht dies die oben gezeigte Semantik? Wie modellierst du einen Baum, in dem du die Eltern-Kind-Beziehung in beide Richtungen durchlaufen kannst?

Zusätzliche Informationen:

Wenn ich das children Feld zu entfernen, der Test bestanden. Das Umkehren der Richtung der Beziehung oder das Vornehmen des Feldtyps (oder Subtyps) Collection macht keinen Unterschied. Die entsprechenden Projektabhängigkeiten sind org.springframework.boot:spring-boot-starter-data-neo4j:jar:1.4.0.RELEASE:compile, org.neo4j:neo4j-ogm-test:jar:2.0.4:test und org.neo4j.test:neo4j-harness:jar:3.0.4:test.

Antwort

1

Wenn Sie eine eingehende @Relationship haben, müssen Sie die Feld-, Accessor- und Mutator-Methoden mit der @Relationship mit Typ und Richtung INCOMING annotieren.

Zweitens glaube ich, dass Iterable für Kinder nicht mit dem OGM-Mapping-Prozess-Implementierungen von List, Vector, Set, SortedSet funktionieren wird.

Wir haben ein Beispiel eines hier Baum: https://github.com/neo4j/neo4j-ogm/blob/2.0/core/src/test/java/org/neo4j/ogm/domain/tree/Entity.java und den Test https://github.com/neo4j/neo4j-ogm/blob/2.0/core/src/test/java/org/neo4j/ogm/persistence/examples/tree/TreeIntegrationTest.java

Edit:

Also habe ich einen Blick auf den Code genommen wieder- Iterable wird wahrscheinlich funktionieren. Könnte eigentlich ein Set sein. In Bezug auf Ihren Kommentar zu parent.children.add (this) ist dies erforderlich, da Ihr Objektmodell andernfalls nicht mit dem übereinstimmt, was Sie vom Graphenmodell erwarten. Wenn der OGM dies zuordnet, könnte es feststellen, dass das Kind einen Elternteil hat, aber der Elternteil das Kind nicht einschließt - und so wird es das eine oder andere als Quelle der Wahrheit auswählen.

+0

Danke, aber das Feld 'children' hat keine Accessor- oder Mutator-Methode: Das Problem wird durch seine bloße Anwesenheit verursacht. Hinzufügen (kommentierte) Getter und Setter-Methoden und/oder die Art des Feldes "List", "Set" oder was auch immer macht das Problem nicht lösen. (Übrigens scheint es unnötigerweise überflüssig, alles kommentieren zu müssen - weißt du, warum das nötig sein sollte?) –

+0

In der 'Entity' sehe ich, dass 'parent.children.add (this) 'von' setParent' aufgerufen wird, was (auf den ersten Blick) scheint das Problem zu lösen. Ich denke, das macht jetzt Sinn, da das Feld veränderbar ist. Aber ich verstehe nicht, warum es nicht funktioniert, um das Feld "Iterable" zu machen - sollte das nicht gelten nach ["... schreibgeschützte Felder sind Iterable ..."] (http: // docs.spring.io/spring-data/neo4j/docs/current/reference/html/#__relationship_connecting_node_entities)? –

+0

Ich habe meine Antwort bearbeitet – Luanne

Verwandte Themen