cancel
Showing results for 
Search instead for 
Did you mean: 

Head's Up! These forums are read-only. All users and content have migrated. Please join us at community.neo4j.com.

Query isn't filtering properly when made within the scope of @Transactional

I have some code that within a transaction, creates a node and a list of edges and calls save(). I then query for the list of edges by a non-keyed field hoping to return one edge, however, the repository returns all the edges. Why is that? I have to call session.clear() before calling my repository query to get the filter to work properly. Is this because of the session cache or something?

@SpringBootTest()
@TestInstance(Lifecycle.PER_CLASS)
@ActiveProfiles(profiles = "embeddedTest")
public class UpdateEdgeTest {

  @Autowired
  private GraphRepositoryTestingHelper graphRepositoryTestingHelper;

  @Autowired
  private StudentRepository studentRepository;

  @Autowired
  private CourseRepository courseRepository;

  @Autowired
  private Session session;

  public void setupData(){
    Student expected = new Student();
    expected.setFirstName("John");
    expected.setLastName("Carter");
    expected.setKey("naturalKey");

    List<EnrolledIn> enrollment = new ArrayList<>();
    EnrolledIn enrolledIn = new  EnrolledIn()
        .setCourse(new Course()
            .setCourseName("neo4j101")
            .setKey("n4j101-jan2020"))
        .setSemester("Jan-2020")
        .setStudent(expected);
    EnrolledIn enrolledIn2 = new  EnrolledIn()
        .setCourse(new Course()
            .setCourseName("java101")
            .setKey("java101-2020"))
        .setSemester("Jan-2020")
        .setStudent(expected);
    enrollment.add(enrolledIn);
    enrollment.add(enrolledIn2);
    expected.setEnrolledIn(enrollment);

    final Student actual = studentRepository.save(expected);
  }

  @AfterAll //must use @AfterAll or else the nodes won't be deleted
  public void after(){
    graphRepositoryTestingHelper.deleteAllNodes();
  }

  @Test
  @Transactional
  public void shouldUpdateOneCourseAndAddNewOneLeavingExistingOnesUnchanged(){

    // setupData() does not work properly.... for some reason attempting to save this data
    // and then call the query findStudentForCourse within the same transaction will not filter out the enrolledIn edges.
    setupData();

    final Optional<Student> astudentWithTwoEdges = studentRepository.findById("naturalKey");
    assertEquals(2, astudentWithTwoEdges.get().getEnrolledIn().size(), "should have 2 courses now");

    // to fix, call session.clear().... not sure why.
    //calling this makes the below query 'findStudentForCourse' work properly... I have no idea why.
    //if this is not called, the query returns ALL the enrolledIn relationships......
    session.clear();
    //find one of the student edges
    final Student studentForCourse = studentRepository.findStudentForCourse("naturalKey", "java101-2020");

    assertEquals(1, studentForCourse.getEnrolledIn().size(), "should only be one due to query filter");
}

@Repository
public interface StudentRepository extends Neo4jRepository<Student, String> {
  @Query("MATCH (student:Student { key: {key}})-[e:ENROLLED_IN]->(c:Course) "
      + "WHERE c.key = {`enrollmentId`} "
      + "RETURN student, e, c")
  Student findStudentForCourse(@Param("key") String key, @Param("enrollmentId") String enrollmentId);
}
4 REPLIES 4

You are absolutely right with your assumption.
Within one transaction one Session will get created with a caching mechanism. When you query for the very same Student, Neo4j-OGM will just add / modify the data on the loaded object but not discard any that is not included in the result.
You could inject the SessionFactory instead of the Session and explicitly open a new Session for the test data setup.
ps. I have the feeling that I answered this somewhere else already around last week.

I tried opening a new session for the test data setup but it still didn't do what I expected. The unit test method is decorated with @Transactional. So I made a new method to be called in a @before setup() method. My query still returned more results than expected, suggesting that the unit test session is still getting data from the call to .save() that was made in the @before setup() method. How can I separate the session? remove @Transactional? (but what if I need a transaction?)

I moved this to a @before method and it's not giving what i'd expect. Is this correct?

       Session session = sessionFactory.openSession();
        try(Transaction tx = session.beginTransaction())
        {
            final Student actual = studentRepository.save(expected);
        }

I also attempted to run that code above in the unit test and received:
java.lang.IllegalStateException: Not allowed to create transaction on shared Session - use Spring transactions instead

Edit3: the shared session is the one that was @autowired. Apparently using it to create a new transaction isn't allowed. I was however able to use the sessionFactory to create a new session and use that new session to make a new transaction. 😄

How can I separate the sessions? It's still using a common session despite creating a new session.

    @Transactional
    public void shouldUpdateOneCourseAndAddNewOneLeavingExistingOnesUnchanged() {
        Student studentForCourse;
        session2 = sessionFactory.openSession();
        try (Transaction tx = session2.beginTransaction()) {
            setupData();
        }
        studentForCourse = studentRepository.findStudentForCourse("naturalKey", "java101-2020");

        //this still fails. It returns 2 courses instead of 1.
        assertEquals(1, studentForCourse.getEnrolledIn().size(), "should only be one due to query filter");
     }

sameerG
Graph Buddy

I think the query is not properly formatted and "WHERE c.key = {enrollmentId} " is getting bypassed and returning all rows instead of specific one.So please read the Cypher Query Language Reference card for proper syntax.