Hot questions for Using Neo4j in transactions

Top Java Programmings / Neo4j / transactions

Question:

I am trying to delete a node.

i know for deleting node first i have to delete relationship.

MATCH (n:`Dummy`)
WHERE n.uuid='1aa41234-aaaa-xxxx-ffff-xxxx11xx0x62'
OPTIONAL MATCH (n)-[r]-()
delete n,r

but its not working

javax.transaction.HeuristicRollbackException: Failed to commit transaction Transaction(80074, owner:"qtp10775679-13464")[STATUS_NO_TRANSACTION,Resources=1], transaction rolled back ---> Transaction handler failed.


Answer:

The error message indicates that a transaction event handler's beforeCommit method threw an exception. If that happens the transaction will be rolled back.

Maybe data/graph.db/messages.log contains a stacktrace. If not I suggest to wrap the contents of your beforeCommit() into a try catch block that catches any exception, prints their stacktrace and rethrows it.

Question:

I am pretty sure that this is a silly question, but I am totally confused here. I am used to code with neo4j-embedded-api and very new to neo4j-jdbc. I want to use Neo4j-JDBC. My Neo4J 2.1.7 instance is hosted on another computer which is accessible via 192.168.1.16:7474

I can create simple programs and write cypher queries and execute them. However, I would like to wrap all of them in a transactional block. Something like this:

    try(Transaction tx = this.graphDatabaseService.beginTx())
    {
        ResultSet resultSet = connect.createStatement().executeQuery(cypherQuery);
        tx.success();
    }

My issue is that I do not know how to get the object of GraphDatabaseService from this:

Neo4jConnection connect = new Driver().connect("jdbc:neo4j://192.168.1.16:7474", new Properties());

Once I have the GraphDatabaseObject, I am assuming that I can use the transactional block like neo4j-embedded-api.

[My Objective] What I am trying to attempt here is to have multiple queries to be sent over the network in a nested-transaction block and if any one of them fails, to rollback all of them.

My failed attempts till now:

I tried to read the neo4j-jdbc project hosted on github and from their test case (link here) I am assuming that to put a code in transaction block you have to connect.setAutoCommit(false); and then use commit() and rollback() functions respectively.


Answer:

If you don't know JDBC then please read up on it, it's a remote API which works by executing statement (Cypher in this case) against a remote API.

There is no GraphDatabaseService object. Only statements and parameters.

For tx handling you either have one tx per statement or if you set connection.setAutoCommit(false); then the transaction runs until you call connection.commit()

Question:

Scenario 1: When I execute the following code without opening a transaction, it returns bookmark successfully.

    Session session2 = sessionFactory.openSession();
    session2.query("MATCH (n) RETURN count(n)", new HashMap<>());
    String lastBookmark = session2.getLastBookmark();
    System.out.println("WITHOUT" + " - " + lastBookmark);

Output:

WITHOUT - neo4j:bookmark:v1:tx6776

When I acquire a transaction explicitly, getLastBookmark() returns null.

    Session session = sessionFactory.openSession();
    session.beginTransaction(Transaction.Type.READ_ONLY);
    session.query("MATCH (n) RETURN count(n)", new HashMap<>());
    System.out.println("With" + " - " + session.getLastBookmark());
    session.getTransaction().close();

Output:

With - null

This always happens irrespective of single or multiple threads.

Scenario 2: Even without transaction when I try to execute getLastBookmark() from multiple threads, it returns null intermittently.

      Thread t2 = new Thread(() -> {
        for (int i = 0; i < 100; i++) {
            Session session = sessionFactory.openSession();
            session.query("MATCH (n) RETURN count(n)", new HashMap<>());
            String lastBookmark = session.getLastBookmark();
            System.out.println("lastBookmark" + " - " + lastBookmark);
            if (lastBookmark == null) {
                throw new RuntimeException(" lastBookmark NULL");
            }
        }
    });

    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 100; i++) {
            Session session = sessionFactory.openSession();
            session.query("MATCH (n) RETURN count(n)", new HashMap<>());
            String lastBookmark = session.getLastBookmark();
            System.out.println("lastBookmark" + " - " + lastBookmark);
            if (lastBookmark == null) {
                throw new RuntimeException(" lastBookmark NULL");
            }
        }
    });

Output:

lastBookmark - neo4j:bookmark:v1:tx6776
lastBookmark - neo4j:bookmark:v1:tx6776
lastBookmark - neo4j:bookmark:v1:tx6776
lastBookmark - null
Exception in thread "Thread-2" java.lang.RuntimeException:  lastBookmark NULL
    at MyTest.lambda$testSession$4(MyTest.java:173)
    at java.lang.Thread.run(Thread.java:745)
lastBookmark - neo4j:bookmark:v1:tx6776
lastBookmark - neo4j:bookmark:v1:tx6776
lastBookmark - neo4j:bookmark:v1:tx6776
lastBookmark - neo4j:bookmark:v1:tx6776
lastBookmark - neo4j:bookmark:v1:tx6776

Any ideas?

I am using neo4j-ogm-core:2.1.5 & neo4j-ogm-bolt-driver:2.1.5 dependencies.


Answer:

The bookmarks are tied to transactions. They are sent by the database at the end of transactions.

When you use query without opening a transaction before, an autocommit transaction is managed by OGM (it is an implicit transaction). The bookmark is available right after executing the query method.

When managing yourself the transaction (explicit transaction), you have to wait the end of the transaction for the bookmark to be available.

Regarding your second point, try updating driver and/or OGM. If the problem is still there, share a sample project reproducing the issue (you can use some templates here), preferably in another question for clarity.

Question:

I have a service for example

public class ServiceA {

    @Transactional
    public void a() {
        ...
        serviceB.b()
        ...
    }
}

that calls a method of another service

public class ServiceB {

    @Transactional
    public void b() {
        ...
        graphRepository.save(..)
        ...
    }
}

that calls a method of a GraphRepository.

If there isn't any problem during the execution of the GraphRepository method everithigs works well, but if an exception occurs in the GraphRepository the rollback doesn't work and all calls to the remote db are blocked.

If I remove the Transactional annotation over the b() everithins works well even if an exception occurs in the GraphRepository.

I can't explain this behavior.

Can you help me?


Answer:

Thank to Luanne and her comment I solved the issue

This is a bug and it is solved in neo4j-ogm 1.1.5-SNAPSHOT

Question:

I'm using:

  • spring-data-neo4j 4.2.0.BUILD-SNAPSHOT
  • neo4j-ogm 2.1.0-SNAPSHOT
  • neo4j 3.0.7

and I'm having problems with the new X-Write HTTP header set by neo4j-ogm.

We use this header in our HAProxy configuration to redirect write transactions to master neo4j node.

Our spring-data-neo4j write transactions are doing multiple calls to neo4j:

  • POST .../db/data/transaction/1 {"statements":[{"statement":"request1"}...]}
  • POST .../db/data/transaction/1 {"statements":[{"statement":"request2"}...]}
  • POST .../db/data/transaction/1/commit

All HTTP REST calls correctly contain the X-WRITE header, except the last call to commit the transaction that seem not to contain this header. So this call is sometimes sent to a slave node where the transaction is not existing.

Is it a bug in neo4j-ogm ?


Answer:

This has been fixed in neo4j-ogm 2.1.1-SNAPSHOT via https://github.com/neo4j/neo4j-ogm/pull/300.

X-WRITE should now appear in the header on commit/rollback.

Question:

I am reading the documentation provided by Neo4j https://neo4j.com/docs/java-reference/current/transactions/, but as the link says, the rules provided in that explanation are valid when talking about the java-extension, what about Neo4j without any extension? I am interested in particular in the default isolation level, what is the default one in the basic case?


Answer:

The default neo4j isolation level in all cases is READ_COMMITTED, which means that a transaction (A) that reads a node/relationship does not block another transaction (B) from writing to that node/relationship before A has completed.

Question:

I am using neo4j 2.1.7 with java.

    try(Transaction transaction = this.graphDatabaseService.beginTx())
    {
        Node user = this.graphDatabaseService.createNode();
        user.setProperty("userId", userId);
        transaction.failure();
    }

So I am getting the object of GraphDatabaseService and creating a new transaction and marking it to rollback. According to their javadocs:

void failure()

Marks this transaction as failed, which means that it will unconditionally be rolled back when close() is called. Once this method has been invoked, it doesn't matter if success() is invoked afterwards -- the transaction will still be rolled back.

But I see that the node gets created no matter what. I tried throwing an exception. I also tried not calling transaction.success() at all. Still I see that the changes get committed and not rolled back. I am not sure of this behaviour and would like an explanation. Thanks.

If you must know, I am trying to build a commit() function with nested transactions such that if any operation fail within the inner transactions, the parent transaction must fail too. However, in the process I found that no matter what I do, the transactions are getting committed.

Update 1:

The embedded version of neo4j works fine. The rest version is causing this trouble. I am using this package for rest:

<dependency>
    <groupId>org.neo4j</groupId>
    <artifactId>neo4j-rest-graphdb</artifactId>
    <version>2.0.1</version>
</dependency>

Answer:

No transactions over REST, at least not for that old version.

There are only transactions over HTTP with the new Cypher Endpoint.

That library is discontinued, I recommend that you use e.g. the JDBC driver or the new implementation that comes with Spring Data REST.

Question:

I'm using:

  • spring-data-neo4j 4.2.0.BUILD-SNAPSHOT
  • neo4j-ogm 2.1.1
  • neo4j 3.0.8

and I'm having a problem with the X-Write HTTP header set by neo4j-ogm.

On write transactions, it is correctly set.

But I'm having a problem with read-only transactions. I have multiple calls to neo4j in my read-only transactions:

  • POST .../db/data/transaction {"statements":[{"statement":"request1"}...]} (X-WRITE: true)
  • POST .../db/data/transaction/1 {"statements":[{"statement":"request2"}...]} (X-WRITE: false)
  • POST .../db/data/transaction/1 {"statements":[{"statement":"request3"}...]} (X-WRITE: false)
  • ...

The problem is that the first request contains the X_WRITE header with the 'true' value. The next requests correctly have the header with the 'false' value.

So the first request is always sent to my master node and I'm not able to send the next requests on a slave node, because all requests inside a transaction must be sent to the same neo4j node.

Looking at neo4j-ogm source code, it seems it could maybe be due to HttpDriver.readOnly() method that is returning false when getCurrentTransaction() is null.


Answer:

This is definitely a bug in the OGM HTTP Driver. Please raise a bug here: https://github.com/neo4j/neo4j-ogm/issues