2016-02-04 3 views
2

Я не уверен, что это вопрос Neo4j или вопрос с Spring Data. Я довольно новичок в Neo4j, поэтому я просто хочу убедиться, что все правильно. Я использую spring-data-neo4j: 4.0.0.RELEASE с экземпляром DB neo4j-community-2.3.1.Весенние данные Neo4j 4отверждающиеся кешированные результаты?

Ситуация в том, что я получаю больше узлов, которые я ожидаю от запросов БД. Если я создаю граф, состоящий из 3-х различных типов узлов:

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

, а затем я выполнить запрос, чтобы получить единый узел узла А я получаю весь граф от узла А к NodeC в результатах запроса.

Кажется, что я получаю кешированные результаты вместо живых результатов из БД. Причина, по которой я говорю это, заключается в том, что если я вызову session.context.clear() после создания графика, запрос больше не возвращает весь граф, включая NodeC, но он все равно возвращает одиночный NodeA вместе со всеми его узлами NodeB ,

Я нашел эту цитату в документации Spring Data Neo4j (http://docs.spring.io/spring-data/neo4j/docs/current/reference/html/):

Заметим, однако, что сессия не всегда возвращают кэшируются объекты так нет никакого риска получения устаревших данных о нагрузке ; он всегда попадает в базу данных .

Я создал небольшой пример приложения для иллюстрации:

сущностей Классы:

@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; 
    } 
} 

Repository:

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() ясно() вызывает здесь результаты:

[{"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"}]}]} 

Обратите внимание, что весь граф возвращается только, когда я прошу его в явном виде, однако я до сих пор получающего NodeB с NodeA.

Мне нужно заполнить ответ на вызов REST, и мне бы не пришлось снимать посторонние объекты, чтобы они не отображались в ответе REST. Должен ли я сделать вызов session.context(). Clear() после каждого доступа к БД, чтобы я не получал «кэшированные» узлы? Есть ли лучший способ сделать звонок для получения более тонкого результата? Можно ли вообще отключить «кеширование»?

ответ

1

Это по дизайну - запрос действительно попадает в базу данных для получения свежих данных, однако, если у объекта были связанные узлы уже в сеансе, то они сохраняются.Обратите внимание, что поведение некоторых из ваших методов тестирования отличается:

Iterable<NodeA> allNodeAs = nodeARep.findAll(); // По умолчанию глубина 1, так что она будет загружать связанные узлы из графика, один хмель прочь

NodeA oneNodeA = nodeARep.findByName("NodeA 1"); // производного искатель, глубина по умолчанию 1 , такое же поведение, как указано выше

NodeA oneNodeA2 = nodeARep.findByNameQuery("NodeA 1"); // Пользовательский запрос возвращает только то, что запрашивает запрос.

Вы хотите сделать session.clear(), а затем загрузить с глубиной 0, если вы используете findAll или find-by-id. Подробное объяснение можно получить здесь https://jira.spring.io/browse/DATAGRAPH-642

+0

Спасибо, Луанна. Я включил все различные методы тестирования, чтобы показать, что результаты были одинаковыми, хотя ожидаемое поведение теста должно было быть иным. Как я уже сказал, я должен вернуть результаты своих запросов БД в качестве ответов на вызовы REST, потребители ответов REST не ожидают, что связанные узлы будут возвращены, поэтому я должен их разбить. Вызов session.clear() после того, как каждый доступ к БД не идеален, но я думаю, нам придется жить с этим пока. Я хотел бы отдать свой голос за устранение такого поведения, как указано в приведенной вами ссылке Джиры. :) –

+0

как продолжение, можете ли вы рассказать мне, является ли сеанс (aka mapping context) глобальным для всех запросов к БД? То есть если другой запрос вносит изменения в NodeB, и я запрашиваю NodeA, я получаю устаревший узел NodeB от моего сеанса с запросом NodeA или получаю обновленный NodeB из другого потока? –

+1

Жизнь сеанса может управляться вами. См. Http://docs.spring.io/spring-data/neo4j/docs/4.0.0.RELEASE/reference/html/#_session_bean Обычно вы хотите, чтобы он жил так долго, как единица работы, поэтому запрос или сеанс, охваченный веб-приложением. Чтобы избежать проблем с целостностью данных, вам нужны свежие данные в начале каждой единицы работы, либо путем повторной выборки данных, либо путем обновления сеанса (очистка/получение нового). – Luanne