【问题标题】:Spring Data Neo4j 4returning cached results?Spring Data Neo4j 4返回缓存的结果?
【发布时间】:2016-02-04 17:41:34
【问题描述】:

我不确定这是 Neo4j 问题还是 Spring Data 问题。我对 Neo4j 还很陌生,所以我只是想确保我做的事情是正确的。我正在使用 spring-data-neo4j:4.0.0.RELEASE 和 neo4j-community-2.3.1 数据库实例。

情况是我从数据库查询中获得了更多我期望返回的节点。如果我创建一个由 3 种不同类型的节点组成的图:

(NodeA)-[:NodeAIncludesNodeB]->(NodeB)-[:NodeBIncludesNodeC]->(NodeC)

然后我运行查询以获取单个 NodeA 节点我在查询结果中收到从 NodeA 到 NodeC 的整个图。

似乎我正在从数据库中获取缓存结果而不是实时结果。我之所以这么说是因为如果我在创建图表后调用 session.context.clear(),查询将不再返回包括 NodeC 在内的整个图表,但它仍然返回单个 NodeA 及其所有 NodeB .

我在 Spring Data Neo4J 文档 (http://docs.spring.io/spring-data/neo4j/docs/current/reference/html/) 中找到了这句话:

但是请注意,Session 永远不会返回缓存的对象,因此 没有在加载时获得陈旧数据的风险;它总是击中 数据库。

我创建了一个小示例应用程序来说明:

实体类:

@NodeEntity
public class NodeA extends BaseNode {

  private String name;
  @Relationship(type = "NodeAIncludesNodeB", direction = "OUTGOING")
  private Set<NodeB> bNodes;

  public NodeA() {}

  public NodeA(String name) {
    this.name = name;
  }
 //getters, setter, equals and hashcode omitted for brevity
}

@NodeEntity
public class NodeB extends BaseNode {

  private String name;
  @Relationship(type = "NodeBIncludesNodeC", direction = "OUTGOING")
  private Set<NodeC> cNodes;

  public NodeB() {}

  public NodeB(String name) {
    this.name = name;
  }
}

@NodeEntity
public class NodeC extends BaseNode {

  private String name;

  public NodeC() {}

  public NodeC(String name) {
    this.name = name;
  }  
}

存储库:

public interface NodeARepository extends GraphRepository<NodeA> {

  public NodeA findByName(String name);

  @Query("MATCH (n:NodeA) WHERE n.name = {nodeName} RETURN n")
  public NodeA findByNameQuery(@Param("nodeName") String name);

  @Query("MATCH (n:NodeA)-[r:NodeAIncludesNodeB]->() WHERE n.name = {nodeName} RETURN r")
  public NodeA findByNameWithBNodes(@Param("nodeName") String name);

  @Query("MATCH (n:NodeA)-[r1:NodeAIncludesNodeB]->()-[r2:NodeBIncludesNodeC]->() WHERE n.name = {nodeName} RETURN r1,r2")
  public NodeA findByNameWithBAndCNodes(@Param("nodeName") String name);
}

测试应用:

@SpringBootApplication
public class ScratchApp implements CommandLineRunner {

  @Autowired
  NodeARepository nodeARep;

  @Autowired
  Session session;

  @SuppressWarnings("unused")
  public static void main(String[] args) {
    ApplicationContext ctx = SpringApplication.run(ScratchApp.class, args);

  }

  @Override
  public void run(String...strings) {

    ObjectMapper mapper = new ObjectMapper();

    NodeA nodeA = new NodeA("NodeA 1");
    NodeB nodeB1 = new NodeB("NodeB 1");
    NodeC nodeC1 = new NodeC("NodeC 1");
    NodeC nodeC2 = new NodeC("NodeC 2");
    Set<NodeC> b1CNodes = new HashSet<NodeC>();
    b1CNodes.add(nodeC1);
    b1CNodes.add(nodeC2);
    nodeB1.setcNodes(b1CNodes);
    NodeB nodeB2 = new NodeB("NodeB 2");
    NodeC nodeC3 = new NodeC("NodeC 3");
    NodeC nodeC4 = new NodeC("NodeC 4");
    Set<NodeC> b2CNodes = new HashSet<NodeC>();
    b2CNodes.add(nodeC3);
    b2CNodes.add(nodeC4);
    nodeB2.setcNodes(b2CNodes);
    Set<NodeB> aBNodes = new HashSet<NodeB>();
    aBNodes.add(nodeB1);
    aBNodes.add(nodeB2);
    nodeA.setbNodes(aBNodes);
    nodeARep.save(nodeA);
//    ((Neo4jSession)session).context().clear();

    try {
      Iterable<NodeA> allNodeAs = nodeARep.findAll();
      System.out.println(mapper.writeValueAsString(allNodeAs));
//      ((Neo4jSession)session).context().clear();

      Iterable<NodeA> allNodeAs2 = nodeARep.findAll();
      System.out.println(mapper.writeValueAsString(allNodeAs2));

      NodeA oneNodeA = nodeARep.findByName("NodeA 1");
      System.out.println(mapper.writeValueAsString(oneNodeA));

      NodeA oneNodeA2 = nodeARep.findByNameQuery("NodeA 1");
      System.out.println(mapper.writeValueAsString(oneNodeA2));

      NodeA oneNodeA3 = session.load(NodeA.class, oneNodeA.getId());
      System.out.println(mapper.writeValueAsString(oneNodeA3));
//      ((Neo4jSession)session).context().clear();

      NodeA oneNodeA4 = nodeARep.findByNameWithBNodes("NodeA 1");
      System.out.println(mapper.writeValueAsString(oneNodeA4));

      NodeA oneNodeA5 = nodeARep.findByNameWithBAndCNodes("NodeA 1");
      System.out.println(mapper.writeValueAsString(oneNodeA5));

    } catch (JsonProcessingException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }

  }
}

以下是测试程序的结果:

[{"id":20154,"name":"NodeA 1","bNodes":[{"id":20160,"name":"NodeB 1","cNodes":[{"id":20155,"name":"NodeC 1"},{"id":20156,"name":"NodeC 2"}]},{"id":20157,"name":"NodeB 2","cNodes":[{"id":20158,"name":"NodeC 3"},{"id":20159,"name":"NodeC 4"}]}]}] 
[{"id":20154,"name":"NodeA 1","bNodes":[{"id":20160,"name":"NodeB 1","cNodes":[{"id":20155,"name":"NodeC 1"},{"id":20156,"name":"NodeC 2"}]},{"id":20157,"name":"NodeB 2","cNodes":[{"id":20158,"name":"NodeC 3"},{"id":20159,"name":"NodeC 4"}]}]}] 
{"id":20154,"name":"NodeA 1","bNodes":[{"id":20160,"name":"NodeB 1","cNodes":[{"id":20155,"name":"NodeC 1"},{"id":20156,"name":"NodeC 2"}]},{"id":20157,"name":"NodeB 2","cNodes":[{"id":20158,"name":"NodeC 3"},{"id":20159,"name":"NodeC 4"}]}]} 
{"id":20154,"name":"NodeA 1","bNodes":[{"id":20160,"name":"NodeB 1","cNodes":[{"id":20155,"name":"NodeC 1"},{"id":20156,"name":"NodeC 2"}]},{"id":20157,"name":"NodeB 2","cNodes":[{"id":20158,"name":"NodeC 3"},{"id":20159,"name":"NodeC 4"}]}]} 
{"id":20154,"name":"NodeA 1","bNodes":[{"id":20160,"name":"NodeB 1","cNodes":[{"id":20155,"name":"NodeC 1"},{"id":20156,"name":"NodeC 2"}]},{"id":20157,"name":"NodeB 2","cNodes":[{"id":20158,"name":"NodeC 3"},{"id":20159,"name":"NodeC 4"}]}]} 
{"id":20154,"name":"NodeA 1","bNodes":[{"id":20157,"name":"NodeB 2","cNodes":[{"id":20158,"name":"NodeC 3"},{"id":20159,"name":"NodeC 4"}]},{"id":20160,"name":"NodeB 1","cNodes":[{"id":20155,"name":"NodeC 1"},{"id":20156,"name":"NodeC 2"}]}]} 
{"id":20154,"name":"NodeA 1","bNodes":[{"id":20157,"name":"NodeB 2","cNodes":[{"id":20159,"name":"NodeC 4"},{"id":20158,"name":"NodeC 3"}]},{"id":20160,"name":"NodeB 1","cNodes":[{"id":20156,"name":"NodeC 2"},{"id":20155,"name":"NodeC 1"}]}]}

请注意,每个查询都返回相同的结果,即使我在除最后两个查询之外的所有查询中只请求一个节点。

如果我取消注释 session.context().clear() 调用这里是结果:

[{"id":20161,"name":"NodeA 1","bNodes":[{"id":20164,"name":"NodeB 2","cNodes":null},{"id":20167,"name":"NodeB 1","cNodes":null}]}]
[{"id":20161,"name":"NodeA 1","bNodes":[{"id":20164,"name":"NodeB 2","cNodes":null},{"id":20167,"name":"NodeB 1","cNodes":null}]}] 
{"id":20161,"name":"NodeA 1","bNodes":[{"id":20164,"name":"NodeB 2","cNodes":null},{"id":20167,"name":"NodeB 1","cNodes":null}]} 
{"id":20161,"name":"NodeA 1","bNodes":[{"id":20164,"name":"NodeB 2","cNodes":null},{"id":20167,"name":"NodeB 1","cNodes":null}]}
{"id":20161,"name":"NodeA 1","bNodes":[{"id":20164,"name":"NodeB 2","cNodes":null},{"id":20167,"name":"NodeB 1","cNodes":null}]}
{"id":20161,"name":"NodeA 1","bNodes":[{"id":20164,"name":"NodeB 2","cNodes":null},{"id":20167,"name":"NodeB 1","cNodes":null}]}
{"id":20161,"name":"NodeA 1","bNodes":[{"id":20164,"name":"NodeB 2","cNodes":[{"id":20165,"name":"NodeC 3"},{"id":20166,"name":"NodeC 4"}]},{"id":20167,"name":"NodeB 1","cNodes":[{"id":20163,"name":"NodeC 2"},{"id":20162,"name":"NodeC 1"}]}]}

请注意,整个图表仅在我明确请求时才返回,但是我仍然收到带有 NodeA 的 NodeB。

我需要填充对 REST 调用的响应,并且我宁愿不必去除无关对象,这样它们就不会出现在 REST 响应中。我是否必须在每次访问数据库后调用 session.context().clear() 以免收到“缓存”节点?有没有更好的方法来调用以获得更细粒度的结果?我可以完全关闭“缓存”吗?

【问题讨论】:

    标签: neo4j spring-data-neo4j-4 neo4j-ogm


    【解决方案1】:

    这是设计使然 - 查询确实会访问数据库以获取新数据,但是,如果实体在会话中已有相关节点,则保留这些节点。请注意,您的某些测试方法的行为是不同的:

    Iterable&lt;NodeA&gt; allNodeAs = nodeARep.findAll(); //默认深度1,所以会从图中加载相关节点,一跳远

    NodeA oneNodeA = nodeARep.findByName("NodeA 1"); //派生查找器,默认深度1,行为同上

    NodeA oneNodeA2 = nodeARep.findByNameQuery("NodeA 1"); //自定义查询,它只会返回查询要求的内容。

    如果您使用的是 findAll 或 find-by-id,您需要先执行 session.clear(),然后执行深度为 0 的加载。这里有详细的解释https://jira.spring.io/browse/DATAGRAPH-642

    【讨论】:

    • 谢谢,卢安妮。我包括了所有不同的测试方法,以表明结果是相同的,即使测试的预期行为应该不同。正如我所说,我必须将我的数据库查询的结果作为对 REST 调用的响应返回,REST 响应的消费者不希望返回相关节点,因此我必须剥离它们。在每次访问数据库后调用 session.clear() 并不理想,但我想我们现在必须忍受它。我现在想投票赞成删除您包含的 Jira 链接中提到的这种行为。 :)
    • 作为后续,您能否告诉我会话(也称为映射上下文)对于对数据库的所有请求是否是全局的? IE。如果另一个请求对 NodeB 进行了更改,而我请求 NodeA,我是从与我请求的 NodeA 的会话中得到一个陈旧的 NodeB,还是从另一个线程接收到更新的 NodeB?
    • 会话的生命周期可以由您管理。请参阅docs.spring.io/spring-data/neo4j/docs/4.0.0.RELEASE/reference/… 通常您希望它与工作单元一样长,因此请求或会话范围在 Web 应用程序中。为避免数据完整性问题,您需要在每个工作单元开始时获取新数据,方法是重新获取数据或刷新会话(清除/获取新会话)
    • 在这里找到了将 Session Bean 的范围定义为“请求”的具体方法:neo4j.com/blog/spring-data-neo4j-4-1-applications(最终视频中最少 4:55)@Override @Bean @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS) public Session getSession() throws Exception { ...} 在您扩展 Neo4jConfiguration 的类中。
    猜你喜欢
    • 1970-01-01
    • 2016-04-19
    • 1970-01-01
    • 1970-01-01
    • 2014-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多