Neo4j Explicit Transactions Using Python

by John Singer December 2, 2020 at 7:08 am

In this post we will look at how you can create and manage explicit transactions in Neo4j using the Python Driver for Neo4j. Earlier posts covered the basics of setting up a connection to Neo4j using the Python driver including how to create driver objects and session objects and use automatic transactions.

This post is part of a series of posts that explains how to write python code that accesses and manipulates data in the Neo4j graph database. You can look at the Idle Python environment I use to run the sample scripts or you can use your favorite Python editor.

The beginning of the script sets things up by creating driver and session objects. This was covered in earlier posts.

from neo4j import GraphDatabase
from neo4j import DEFAULT_DATABASE

#
# AUTOMATIC TRANSACTION DEMO
#
print("tutorialtransaction1.py - starting")
# create the uri for the connection
uri = "neo4j://localhost:7687"
# create a driver object
driver = GraphDatabase.driver(uri, auth=("neo4j", "xxx"))
# create a session that connects to the default database
session = driver.session(database=DEFAULT_DATABASE)

Remember to change the password on the line that creates the driver object (the code shows it as “xxx”). The code above does the needed imports, and creates a driver and session object.

To make this script re-runnable, it starts out by deleting the two nodes and the relationship. See the code below:

# delete all the tutorial data
print("delete all tutorial data")
result = session.run("match (n:demoPerson) detach delete n")
resultSummary = result.consume()
print("result counters:{}".format(resultSummary.counters))

Neo4j Explicit Transactions Using Python

The tutorial example (like the automatic transaction tutorial) will create two nodes and then connect those nodes with a relationship. In this example however, we will use one transaction to create the two nodes and the relationship. The three cypher creates will run inside one transaction which will insure that either all three succeed or they all fail. Why would we want to do this? Our example is kind of trivial, but imagine you are transferring money from your savings account to your checking account. Two queries are needed, the first one reduces the balance in your savings account and the second query increases your balance in the checking account. Now it would be a real problem if the first query worked and the second one failed for some reason. To solve this problem we run both queries in a single transaction or “unit of work”.

To start a transaction you must first create a transaction object.

txn = session.begin_transaction()

The session object is used to create a new transaction object by calling the “begin_transaction” method.

Now we can run some cypher create statements.

query = 'create (n:demoPerson {name: $parm1, country: $parm2}) return n'

result = txn.run(query, parm1="Fred", parm2="US")


result = txn.run(query, {"parm1":"Barney", "parm2":"US"})

result = txn.run('''match (p1:demoPerson {name:"Fred", country:"US"}), (p2:demoPerson {name:"Fred", country:"US"})
                      with p1,p2
                      create (p1)-[r:KNOWS]->(p2)
                      return r''')

We use the “run” method of the transaction object to run queries inside the transaction. In the previous post on automatic transactions, we used the “run” method of the session object (which automatically handled creating a transaction) to run our cypher queries. Assuming no errors occur, the three queries above will create the two nodes and the relationship but nobody else on the system can see them until you “commit” the transaction.

Commit and Rollback

txn.commit()

The commit method tells the Neo4j server to make all of the updates permanent in the database. Prior to the commit statement, the create statements did their job, i.e. the nodes were created but they were not visible to other users of the system.

Alternatively, if some error occurs in one of the three create statements, the program can issue a “rollback” which tells the Neo4j server to remove all the work performed by the transaction from the database.

txn.rollback()

In the tutorial script the rollback statement is commented out. You can experiment with running the commit or the rollback (you can’t do both as an error will occur if you try to rollback the transaction after committing it).

The transaction object also has a “close” method.

txn.close()
print("close transaction closed={}".format(txn.closed()))

The close method is primarily for convenience. When you call the close method, the system will rollback the transaction if it was never committed. You can test to see if a transaction has been closed by calling the “closed()” method. This will return true or false. Finally, if you do a commit or a rollback, this will also close the transaction (i.e. txn.closed() will return TRUE). It doesn’t hurt to call the close method after a commit or rollback although it seems redundant. My preference is to code logic that includes both a commit and a rollback depending on any errors occurring so I’m not sure of the usefulness of the close method. We will look at using python try/except blocks to handle error situations in a different post.

Summary

In this point we looked at how to run Neo4j explicit transactions using Python. Explicit transactions are used to create two nodes and then create a relationship between them. Here’s the entire script:

from neo4j import GraphDatabase
from neo4j import DEFAULT_DATABASE

#
# EXPLICIT TRANSACTION DEMO
#
print("tutorialtransaction2.py - starting")
# create the uri for the connection
uri = "neo4j://localhost:7687"
# create a driver object
driver = GraphDatabase.driver(uri, auth=("neo4j", "nachodog"))
# create a session that connects to the default database
session = driver.session(database=DEFAULT_DATABASE)

# delete all the tutorial data
print("delete all tutorial data")
result = session.run("match (n:demoPerson) detach delete n")
resultSummary = result.consume()
print("result counters:{}".format(resultSummary.counters))

# create two nodes and link them together
txn = session.begin_transaction()
query = 'create (n:demoPerson {name: $parm1, country: $parm2}) return n'
print("create a demoPerson node")
result = txn.run(query, parm1="Fred", parm2="US")

print("create another demoPerson node")
result = txn.run(query, {"parm1":"Barney", "parm2":"US"})

print("create a relationship between the two nodes")
result = txn.run('''match (p1:demoPerson {name:"Fred", country:"US"}), (p2:demoPerson {name:"Fred", country:"US"})
                      with p1,p2
                      create (p1)-[r:KNOWS]->(p2)
                      return r''')

# commit the transaction
txn.commit()
print("commit transaction closed={}".format(txn.closed()))

# rollback the transaction
#txn.rollback()
#print("rollback transaction closed={}".format(txn.closed()))

txn.close()
print("close transaction closed={}".format(txn.closed()))
      
# clean up
session.close()
driver.close()
print("tutorialtransaction2.py - driver closed")

The entire script is designed so you can run it repeatedly and since it starts out by deleting the tutorial data it will not create a lot of duplicate Fred and Barney nodes.

No Code Solution

Want to query, edit, and delete nodes and relationships without writing any code? Check out NodeEra – the worlds leading Neo4j data modeling and management tool.

Add Comment