Java 8: Lambda Expressions vs Auto Closeable
If you used earlier versions of Neo4j via its Java API with Java 6 you probably have code similar to the following to ensure write operations happen within a transaction:
public class StylesOfTx
{
public static void main( String[] args ) throws IOException
{
String path = "/tmp/tx-style-test";
FileUtils.deleteRecursively(new File(path));
GraphDatabaseService db = new GraphDatabaseFactory().newEmbeddedDatabase( path );
Transaction tx = db.beginTx();
try
{
db.createNode();
tx.success();
}
finally
{
tx.close();
}
}
}
In Neo4j 2.0 Transaction started extending AutoCloseable which meant that you could use 'try with resources' and the 'close' method would be automatically called when the block finished:
public class StylesOfTx
{
public static void main( String[] args ) throws IOException
{
String path = "/tmp/tx-style-test";
FileUtils.deleteRecursively(new File(path));
GraphDatabaseService db = new GraphDatabaseFactory().newEmbeddedDatabase( path );
try ( Transaction tx = db.beginTx() )
{
Node node = db.createNode();
tx.success();
}
}
}
This works quite well although it’s still possible to have transactions hanging around in an application when people don’t use this syntax - the old style is still permissible.
In Venkat Subramaniam’s Java 8 book he suggests an alternative approach where we use a lambda based approach:
public class StylesOfTx
{
public static void main( String[] args ) throws IOException
{
String path = "/tmp/tx-style-test";
FileUtils.deleteRecursively(new File(path));
GraphDatabaseService db = new GraphDatabaseFactory().newEmbeddedDatabase( path );
Db.withinTransaction(db, neo4jDb -> {
Node node = neo4jDb.createNode();
});
}
static class Db {
public static void withinTransaction(GraphDatabaseService db, Consumer<GraphDatabaseService> fn) {
try ( Transaction tx = db.beginTx() )
{
fn.accept(db);
tx.success();
}
}
}
}
The 'withinTransaction' function would actually go on GraphDatabaseService or similar rather than being on that Db class but it was easier to put it on there for this example.
A disadvantage of this style is that you don’t have explicit control over the transaction for handling the failure case - it’s assumed that if 'tx.success()' isn’t called then the transaction failed and it’s rolled back. I’m not sure what % of use cases actually need such fine grained control though.
Brian Hurt refers to this as the 'hole in the middle pattern' and I imagine we’ll start seeing more code of this ilk once Java 8 is released and becomes more widely used.
About the author
I'm currently working on short form content at ClickHouse. I publish short 5 minute videos showing how to solve data problems on YouTube @LearnDataWithMark. I previously worked on graph analytics at Neo4j, where I also co-authored the O'Reilly Graph Algorithms Book with Amy Hodler.