Transactions and relationship entities mapping pro

2019-07-27 06:39发布

问题:

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

回答1:

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.



回答2:

Firstly, please upgrade from OGM 2.0.6-SNAPSHOT to 2.1.0-SNAPSHOT. I have noticed some off behaviour in the former which might be one part of the issue.

Now on to your test. There are several things going on here which are worth investigating.

  • Use of @DirtiesContext: You don't seem to be touching the context and if you are using it to reset the context between tests so you get a new Session/Transaction then that's going about it the wrong way. Just use @Transactional instead. The Spring JUnit runner will treat this in a special manner (see next point).
  • Being aware that Transactional tests automatically roll back: Jasper is right. Spring Integration Tests will always roll back by default. If you want to make sure your JUnit test commits then you will have to @Commit it. A good example of how to set up your test can be seen here.
  • Knowing how Spring Transaction proxies work. On top of all this confusion you have to make sure you don't simply call transactional method to transactional method in the same class and expect Spring's Transactional behaviour to apply. A quick write up on why can be seen here.

If you address those issues everything should be fine.