Search code examples
springneo4jspring-data-neo4j-4neo4j-ogm

Transactions and relationship entities mapping problems with Neo4j OGM


Versions used: spring-data-neo4j 4.2.0-BUILD-SNAPSHOT / neo4j-ogm 2.0.6-SNAPSHOT

I'm having problems to correctly fetch relationship entities.

The following fetch calls don't return consistent results (executed in the same transaction):

  1. session.query("MATCH (:A)-[b:HAS_B]-(:C) RETURN count(b) as count") returns 1
  2. session.query("MATCH (:A)-[b:HAS_B]-(:C) RETURN b") correctly returns the relationship entity as a RelationshipModel object
  3. session.query(B.class, "MATCH (:A)-[b:HAS_B]-(:C) RETURN b") returns null !

Important remark: When all operations (create, fetch) are done in the same transaction, it seems to be fine.

I have been able to implement a workaround by using session.query(String, Map) to query the relationship entity and map it by myself into my POJO.

@NodeEntity
public class A {
    public A () {}
    public A (String name) {
        this.name = name;
    }

    @GraphId
    private Long graphId;

    private String name;

    @Relationship(type="HAS_B", direction=Relationship.OUTGOING)
    private B b;
}

@RelationshipEntity(type="HAS_B")
public class B {
    public B () {}
    public B (String name, A a, C c) {
        this.name = name;
        this.a = a;
        this.c = c;
    }

    @GraphId
    private Long graphId;

    @StartNode
    private A a;

    @EndNode
    private C c;

    private String name;
}

@NodeEntity
public class C {
    public C () {}
    public C (String name) {
        this.name = name;
    }

    @GraphId
    private Long graphId;

    private String name;
}

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader=AnnotationConfigContextLoader.class, classes={MyTest.TestConfiguration.class})
public class MyTest {
    @Autowired
    private MyBean myBean;

    @Configuration
    @EnableAutoConfiguration
    @EnableTransactionManagement
    @EnableNeo4jRepositories("com.nagra.ml.sp.cpm.core.repositories")
    public static class TestConfiguration {
        @Bean
        public org.neo4j.ogm.config.Configuration configuration() {
            org.neo4j.ogm.config.Configuration config = new org.neo4j.ogm.config.Configuration();
            config.driverConfiguration().setDriverClassName("org.neo4j.ogm.drivers.embedded.driver.EmbeddedDriver");
            return config;
        }
        @Bean
        public SessionFactory sessionFactory() {
            return new SessionFactory(configuration(), "com.nagra.ml.sp.cpm.model");
        }
        @Bean
        public Neo4jTransactionManager transactionManager() {
            return new Neo4jTransactionManager(sessionFactory());
        }
        @Bean
        public MyBean myBean() {
            return new MyBean();
        }
    }

    @Test
    public void alwaysFails() {
        myBean.delete();
        myBean.create("1");
        try { Thread.sleep(2000); } catch (InterruptedException e) {} //useless
        myBean.check("1"); // FAILS HERE !
    }

    @Test
    public void ok() {
        myBean.delete();
        myBean.createAndCheck("2");
    }
}

@Transactional(propagation = Propagation.REQUIRED)
public class MyBean {

    @Autowired
    private Session neo4jSession;

    public void delete() {
        neo4jSession.query("MATCH (n) DETACH DELETE n", new HashMap<>());
    }

    public void create(String suffix) {
        C c = new C("c"+suffix);
        neo4jSession.save(c);
        A a = new A("a"+suffix);
        neo4jSession.save(a);
        B bRel = new B("b"+suffix, a, c);
        neo4jSession.save(bRel);
    }

    public void check(String suffix) {
        //neo4jSession.clear(); //Not working even with this
        Number countBRels = (Number) neo4jSession.query("MATCH (:A)-[b:HAS_B]-(:C) WHERE b.name = 'b"+suffix+"' RETURN count(b) as count", new HashMap<>()).iterator().next().get("count");
        assertEquals(1, countBRels.intValue()); // OK
        Iterable<B> bRels = neo4jSession.query(B.class, "MATCH (:A)-[b:HAS_B]-(:C) WHERE b.name = 'b"+suffix+"' RETURN b", new HashMap<>());
        boolean relationshipFound = bRels.iterator().hasNext();
        assertTrue(relationshipFound); // FAILS HERE !
    }

    public void createAndCheck(String suffix) {
        create(suffix);
        check(suffix);
    }
}

Solution

  • This query session.query(B.class, "MATCH (:A)-[b:HAS_B]-(:C) RETURN b") returns only the relationship but not the start node or end node and so the OGM cannot hydrate this. You need to always return the start and end node along with the relationship like session.query(B.class, "MATCH (a:A)-[b:HAS_B]-(c:C) RETURN a,b,c")

    The reason it appears to work when you both create and fetch data in the same transaction is that the session already has a cached copy of a and c and hence b can be hydrated with cached start and end nodes.