代码之家  ›  专栏  ›  技术社区  ›  ThirstForKnowledge

RelationshipEntity的更新重置了Neo4j中其他丰富关系的值

  •  0
  • ThirstForKnowledge  · 技术社区  · 6 年前

    1问题摘要

    RelationshipEntity 似乎 就像事务被回滚一样。我的期望是只有 关系实体 检查中的关系被更新,所有其他关系未被触及。

    2初始情况

    • 运行Neo4j 3.4.7、Spring Boot v2.0.5.RELEASE、Spring v5.0.9.RELEASE
    • 没有显式使用事务
    • 简化图架构:

    reduced graph schema

    三。目标实际比较

    3.1. 时间顺序

    根据图表模式的简化用例可以总结如下:

    • ClassC 节点
    • 为他们找到各种相关的 ClassB ClassA )
    • 对于每个已识别的 创建 ClassD B类 和丰富的关系 CDMapping C类

    所描述的完整块在第一次运行时运行良好。各种各样的 RelationshipEntitys 之间 C类 D类 设置了“Default Value”属性后,最后一个rich关系将接收预期的值“Special Value”。

    after first run

    3.3详细问题

    关系实体 C类 D类 -上一次运行的属性被意外地设置回“Default Value”,替换原来的“Special Value”。

    during second run

    3.4 Neo4j生成的查询

    cdMappingDAO.save(cdMapping); :

    UNWIND {rows} as row MATCH (startNode) WHERE ID(startNode) = row.startNodeId WITH row,startNode MATCH (endNode) WHERE ID(endNode) = row.endNodeId CREATE (startNode)-[rel:`MAPS_TO`]->(endNode) SET rel += row.props RETURN row.relRef as ref, ID(rel) as id, {type} as type with params {type=rel, rows=[{startNodeId=91, relRef=-45, endNodeId=115, props={attribute=Default Value}}]}
    UNWIND {rows} as row MATCH (startNode) WHERE ID(startNode) = row.startNodeId WITH row,startNode MATCH (endNode) WHERE ID(endNode) = row.endNodeId MERGE (startNode)-[rel:`CONTAINS`]->(endNode) RETURN row.relRef as ref, ID(rel) as id, {type} as type with params {type=rel, rows=[{startNodeId=88, relRef=-49, endNodeId=115, props={}}, {startNodeId=92, relRef=-51, endNodeId=91, props={}}, {startNodeId=88, relRef=-54, endNodeId=93, props={}}, {startNodeId=89, relRef=-56, endNodeId=94, props={}}, {startNodeId=92, relRef=-57, endNodeId=90, props={}}]}
    UNWIND {rows} as row MATCH (n) WHERE ID(n)=row.nodeId SET n:`ClassA`:`Entity` SET n += row.props RETURN row.nodeId as ref, ID(n) as id, {type} as type with params {type=node, rows=[{nodeId=92, props={name=Class A}}]}
    UNWIND {rows} as row MATCH (n) WHERE ID(n)=row.nodeId SET n:`ClassB`:`Entity` SET n += row.props RETURN row.nodeId as ref, ID(n) as id, {type} as type with params {type=node, rows=[{nodeId=88, props={name=Class B1}}, {nodeId=89, props={name=Class B2}}]}
    UNWIND {rows} as row MATCH (n) WHERE ID(n)=row.nodeId SET n:`ClassD`:`Entity` SET n += row.props RETURN row.nodeId as ref, ID(n) as id, {type} as type with params {type=node, rows=[{nodeId=115, props={}}, {nodeId=93, props={}}, {nodeId=94, props={}}]}
    UNWIND {rows} as row MATCH (n) WHERE ID(n)=row.nodeId SET n:`ClassC`:`Entity` SET n += row.props RETURN row.nodeId as ref, ID(n) as id, {type} as type with params {type=node, rows=[{nodeId=90, props={name=Class C1}}, {nodeId=91, props={name=Class C2}}]}
    UNWIND {rows} AS row MATCH ()-[r]-() WHERE ID(r) = row.relId SET r += row.props RETURN ID(r) as ref, ID(r) as id, {type} as type with params {rows=[{relId=104, props={attribute=Default Value}}, {relId=106, props={attribute=Default Value}}], type=rel}
    

    4有待解决的挑战

    你能给我一个建议,为什么属性值会被重置,由什么组件重置?我怎样才能保证 关系实体

    5代码段

    @Component
    public class GraphHandler implements CommandLineRunner {
      private ClassADAO classADAO;
      private ClassBDAO classBDAO;
      private ClassCDAO classCDAO;
      private ClassDDAO classDDAO;
      private CDMappingDAO cdMappingDAO;
      private SessionFactory sessionFactory;
    
    
      @Autowired
      public GraphHandler(ClassADAO classADAO, ClassBDAO classBDAO, ClassCDAO classCDAO, ClassDDAO classDDAO, CDMappingDAO cdMappingDAO, SessionFactory sessionFactory) {
        this.classADAO = classADAO;
        this.classBDAO = classBDAO;
        this.classCDAO = classCDAO;
        this.classDDAO = classDDAO;
        this.cdMappingDAO = cdMappingDAO;
        this.sessionFactory = sessionFactory;
      }
    
    
      public void run(String... args) {
        createInitialModel();
        runUseCase();
      }
    
    
      private void createInitialModel() {
        ClassA classA = new ClassA("Class A");
        ClassB classB1 = new ClassB("Class B1");
        ClassB classB2 = new ClassB("Class B2");
        ClassC classC1 = new ClassC("Class C1");
        ClassC classC2 = new ClassC("Class C2");
    
        classA.getClassBs().addAll(Arrays.asList(classB1, classB2));
        classA.getClassCs().addAll(Arrays.asList(classC1, classC2));
    
        classADAO.save(classA);
        classBDAO.save(classB1);
        classBDAO.save(classB2);
        classCDAO.save(classC1);
        classCDAO.save(classC2);
      }
    
    
      private void runUseCase() {
        Iterable<ClassC> classCs = classCDAO.findAll();
        for (ClassC classC : classCs) {
    
          ClassD rememberedClassD = null;
          List<ClassB> classBs = classBDAO.findClassBSelection(classC.getId());
          for (ClassB classB : classBs) {
    
            ClassD classD = new ClassD();
            classD.setClassB(classB);
            classB.getClassDs().add(classD);
            classDDAO.save(classD);
            rememberedClassD = classD;
    
            CDMapping cdMapping = new CDMapping(classC, classD, "Default Value");
            cdMappingDAO.save(cdMapping); // <-- here the problem occurs
          }
    
          // choosing the last created relationship (ClassC-ClassD) and mark it
          CDMapping relationship = cdMappingDAO.getRelationshipByClassCAndClassD(classC.getId(), rememberedClassD.getId());
          relationship.setAttribute("Special Value");
          cdMappingDAO.save(relationship);
        }
      }
    }
    

    5.2 CDM映射

    @RelationshipEntity(type = "MAPS_TO")
    public class CDMapping {
      @Id
      @GeneratedValue
      private Long id;
    
      @StartNode
      private ClassC classC;
    
      @EndNode
      private ClassD classD;
    
      private String attribute;
    
      public CDMapping(ClassC classC, ClassD classD, String attribute) {
        this.classC = classC;
        this.classD = classD;
        this.attribute = attribute;
    
        classC.getCdMappings().add(this);
        classD.getCdMappings().add(this);
      }
    
      // default constructor, getter and setter here
    }
    

    5.3 A类

    @NodeEntity
    public class ClassA extends Entity {
      private String name;
    
      @Relationship(type = "CONTAINS")
      private List<ClassC> classCs = new ArrayList<>();
    
      @Relationship(type = "MAPS_TO")
      private List<ClassB> classBs = new ArrayList<>();
    
      // default constructor, getter and setter here
    }
    

    @NodeEntity
    public class ClassB extends Entity {
      private String name;
    
      @Relationship(type = "MAPS_TO", direction = Relationship.INCOMING)
      private List<ClassA> classAs = new ArrayList<>();
    
    
      @Relationship(type = "CONTAINS")
      private List<ClassD> classDs = new ArrayList<>();
    
      // default constructor, getter and setter here
    }
    

    5.5 C类

    @NodeEntity
    public class ClassC extends Entity {
      private String name;
    
      @Relationship(type = "CONTAINS", direction = Relationship.INCOMING)
      private ClassA classA;
    
      @Relationship(type = "MAPS_TO")
      private List<CDMapping> cdMappings = new ArrayList<>();
    
      // default constructor, getter and setter here
    }
    

    5.6 D类

    @NodeEntity
    public class ClassD extends Entity {
      @Relationship(type = "CONTAINS", direction = Relationship.INCOMING)
      private ClassB classB;
    
      @Relationship(type = "MAPS_TO", direction = Relationship.INCOMING)
      private List<CDMapping> cdMappings = new ArrayList<>();
    
      // default constructor, getter and setter here
    }
    

    更新

    @Repository
    public interface CDMappingDAO extends Neo4jRepository<CDMapping, Long> {
    
      @Query("MATCH (classC:ClassC)-[relationship:MAPS_TO]-(classD:ClassD) WHERE id(classC)={classCId} AND id(classD)={classDId} RETURN classC, relationship, classD;")
      CDMapping getRelationshipByClassCAndClassD(@Param("classCId") Long classCId, @Param("classDId") Long classDId);
    }
    

    5.8等级ADAO/ClassCDAO/ClassDDAO

    @Repository
    public interface ClassADAO extends Neo4jRepository<ClassA, Long> {
    }
    

    除了第一个 Neo4jRepository 键入 ClassCDAO ClassDDAO

    @Repository
    public interface ClassBDAO extends Neo4jRepository<ClassB, Long> {
      @Query("MATCH (classC:ClassC)<-[:CONTAINS]-(:ClassA)-[:MAPS_TO]->(classB:ClassB) WHERE id(classC)={classCId} RETURN classB;")
      List<ClassB> findClassBSelection(@Param("classCId") Long classCId);
    }
    
    1 回复  |  直到 6 年前
        1
  •  1
  •   Michael Simons    6 年前

    解决方法之一是让代码保持原样:

    runUseCase 一个在你的房间里 GraphHandler 保存 CDMapping 深度为0时:

    private void runUseCase() {
      Iterable<ClassC> classCs = classCDAO.findAll();
      for (ClassC classC : classCs) {
    
        ClassD rememberedClassD = null;
        List<ClassB> classBs = classBDAO.findClassBSelection(classC.getId());
        for (ClassB classB : classBs) {
    
          ClassD classD = new ClassD();
          classD.setClassB(classB);
          classB.getClassDs().add(classD);
          classDDAO.save(classD);
          rememberedClassD = classD;
    
          CDMapping cdMapping = new CDMapping(classC, classD, "Default Value");
          cdMappingDAO.save(cdMapping, 0); // <-- here the problem occurs
        }
    
        // choosing the last created relationship (ClassC-ClassD) and mark it
        CDMapping relationship = cdMappingDAO.getRelationshipByClassCAndClassD(classC.getId(), rememberedClassD.getId());
        relationship.setAttribute("Special Value");
        cdMappingDAO.save(relationship, 0);
      }
    }
    

    我建议进行以下更改,添加一些更正确的事务边界。

    介绍一个服务类,比如 SomeGraphBasedService . 由于Springs声明性事务是如何工作的,因此需要一个专用类。两种方法, createInitialModel 运行用例 现在,每个事务跨越一个事务,所有DAO方法都参与其中。请特别注意我的评论,我只保存了最顶层的父类 :

    import java.util.Arrays;
    import java.util.List;
    
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    @Service
    public class SomeGraphBasedService {
        private final ClassADAO classADAO;
        private final ClassBDAO classBDAO;
        private final ClassCDAO classCDAO;
        private final ClassDDAO classDDAO;
        private final CDMappingDAO cdMappingDAO;
    
        public SomeGraphBasedService(ClassADAO classADAO, ClassBDAO classBDAO,
            ClassCDAO classCDAO, ClassDDAO classDDAO, CDMappingDAO cdMappingDAO) {
            this.classADAO = classADAO;
            this.classBDAO = classBDAO;
            this.classCDAO = classCDAO;
            this.classDDAO = classDDAO;
            this.cdMappingDAO = cdMappingDAO;
        }
    
        @Transactional
        public void createInitialModel() {
            ClassA classA = new ClassA("Class A");
            ClassB classB1 = new ClassB("Class B1");
            ClassB classB2 = new ClassB("Class B2");
            ClassC classC1 = new ClassC("Class C1");
            ClassC classC2 = new ClassC("Class C2");
    
            classA.getClassBs().addAll(Arrays.asList(classB1, classB2));
            classB1.getClassAs().add(classA);
            classB2.getClassAs().add(classA);
            classA.getClassCs().addAll(Arrays.asList(classC1, classC2));
    
            classADAO.save(classA);
            // No need to save them one by one, the releationships
            // take care of that while saving the parent class at the top
            /*
            classBDAO.save(classB1);
            classBDAO.save(classB2);
            classCDAO.save(classC1);
            classCDAO.save(classC2);
            */
        }
    
        @Transactional
        public void runUseCase() {
    
            Iterable<ClassC> classCs = classCDAO.findAll();
            for (ClassC classC : classCs) {
    
                ClassD rememberedClassD = null;
                List<ClassB> classBs = classBDAO.findClassBSelection(classC.getId());
                for (ClassB classB : classBs) {
    
                    ClassD classD = new ClassD();
                    classD.setClassB(classB);
                    classB.getClassDs().add(classD);
                    classDDAO.save(classD);
                    rememberedClassD = classD;
    
                    CDMapping cdMapping = new CDMapping(classC, classD, "Default Value");
                    cdMappingDAO.save(cdMapping); // <-- here the problem occurs
                }
    
                // choosing the last created relationship (ClassC-ClassD) and mark it
                CDMapping relationship = cdMappingDAO
                    .getRelationshipByClassCAndClassD(classC.getId(), rememberedClassD.getId());
                relationship.setAttribute("Special Value");
                cdMappingDAO.save(relationship);
            }
    
            Iterable<CDMapping> f = cdMappingDAO.findAll();
            for (CDMapping ff : f) {
                System.out.println(ff);
            }
        }
    }
    

    然后您的命令行运行程序变为:

    import org.springframework.boot.CommandLineRunner;
    import org.springframework.stereotype.Component;
    
    @Component
    public class GraphHandler implements CommandLineRunner {
        private final SomeGraphBasedService someGraphBasedService;
    
        public GraphHandler(SomeGraphBasedService someGraphBasedService) {
            this.someGraphBasedService = someGraphBasedService;
        }
    
        public void run(String... args) {
            this.someGraphBasedService.createInitialModel();
            this.someGraphBasedService.runUseCase();
    
        }
    }
    

    虽然第一种解决方案显然是一种变通方法,但另一种解决方案是我在现实场景中更喜欢的解决方案。

    CDMapping{id=21, classC=ClassC{name='Class C1'}, classD=ClassD{classB=ClassB{name='Class B1'}}, attribute='Default Value'}
    CDMapping{id=23, classC=ClassC{name='Class C1'}, classD=ClassD{classB=ClassB{name='Class B2'}}, attribute='Special Value'}
    CDMapping{id=25, classC=ClassC{name='Class C2'}, classD=ClassD{classB=ClassB{name='Class B1'}}, attribute='Default Value'}
    CDMapping{id=27, classC=ClassC{name='Class C2'}, classD=ClassD{classB=ClassB{name='Class B2'}}, attribute='Special Value'}