javaneo4jspring-data-neo4jspring-data-neo4j-5

How to mark a custom Spring Data Neo4j 5.0.3 cypher query as read-only


I am currently working on a Spring Data Neo4j 5.0.3 REST API application that interfaces with a Neo4j 3.3.1 causal cluster consisting of 3 core nodes (1 leader and 2 followers). For better or for worse, we are also submitting a lot of custom cypher queries to the database using session.query a la SQL prepared statements. When we do this, we notice that virtually none of our custom Cypher submitted via session.query gets sent to any of the read-only followers.

I've cracked into the code and noticed that within Neo4jSession, the query method always creates a transaction with type READ_WRITE. Is there any way to get around this so that our queries get distributed across our cluster correctly?

I have also tried marking the appropriate methods with @Transactional(readOnly = true) but it doesn't seem to work. When I did into Neo4jTransactionManager, I see the following around line 218:

private Transaction.Type getTransactionType(TransactionDefinition definition, Neo4jTransactionObject txObject) {
    Transaction.Type type;
    if (definition.isReadOnly() && txObject.isNewSessionHolder()) {
        type = Transaction.Type.READ_ONLY;
    } else if (txObject.transactionData != null) {
        type = txObject.transactionData.type();
    } else {
        type = Transaction.Type.READ_WRITE;
    }
    return type;
}

What does that second condition, isNewSessionHolder, in the first branch mean? Within the context of a single HTTP API call, we call out to the database at least 2 times, so by the second query, I believe this condition always returns false.

Given these observations, are there any easy ways forward for making my queries be respected as read-only?


Solution

  • First part regarding Spring: Due to limitations of Spring AOP, it is not possible to have multiple isolated transactions within one class. The best solution would be to separate the calling code from the transactional methods in different classes. Then the @Transactional(readOnly = true) will work.

    Second part regarding OGM's session.query calls: If your unit of work participates in an existing READ_WRITE transaction, e.g. this happens because of the @Transactional AOP problem above, there is no way to set the type to READ_ONLY. As a default OGM will always create a READ_WRITE transaction if no explicit type is set.

    tl;dr;

    There are two solutions for the problem in general:

    1. Extract the @Transactional methods into another class and leave the caller code in the existing one.
    2. Create the Session object manually by injecting the SessionFactory and create a transaction with READ_ONLY type. (and remove the @Transactional annotation)

    (as answered in the Neo4j user slack)