32.11. How to create unique nodes

32.11.1. Get or create unique node using Cypher and unique constraints
32.11.2. Get or create unique node using a legacy index
32.11.3. Pessimistic locking for node creation

This section is about how to ensure uniqueness of a property when creating nodes. For an overview of the topic, see Section 16.6, “Creating unique nodes”.

32.11.1. Get or create unique node using Cypher and unique constraints

Create a Cypher execution engine and a unique constraint. 

try ( Transaction tx = graphdb.beginTx() )
{
    graphdb.schema()
            .constraintFor( DynamicLabel.label( "User" ) )
            .assertPropertyIsUnique( "name" )
            .create();
    tx.success();
}

return new ExecutionEngine( graphdb() );

Use MERGE to create a unique node. 

Node result = null;
ResourceIterator<Node> resultIterator = null;
try ( Transaction tx = graphDb.beginTx() )
{
    String queryString = "MERGE (n:User {name: {name}}) RETURN n";
    Map<String, Object> parameters = new HashMap<>();
    parameters.put( "name", username );
    resultIterator = engine.execute( queryString, parameters ).columnAs( "n" );
    result = resultIterator.next();
    tx.success();
    return result;
}

32.11.2. Get or create unique node using a legacy index

[Important]Important

While this is a working solution, please consider using the preferred Section 32.11.1, “Get or create unique node using Cypher and unique constraints” instead.

By using put-if-absent functionality, entity uniqueness can be guaranteed using an index.

Here the index acts as the lock and will only lock the smallest part needed to guarantee uniqueness across threads and transactions. To get the more high-level get-or-create functionality make use of UniqueFactory as seen in the example below.

Create a factory for unique nodes at application start. 

try ( Transaction tx = graphDb.beginTx() )
{
    UniqueFactory.UniqueNodeFactory result = new UniqueFactory.UniqueNodeFactory( graphDb, "users" )
    {
        @Override
        protected void initialize( Node created, Map<String, Object> properties )
        {
            created.addLabel( DynamicLabel.label( "User" ) );
            created.setProperty( "name", properties.get( "name" ) );
        }
    };
    tx.success();
    return result;
}

Use the unique node factory to get or create a node. 

try ( Transaction tx = graphDb.beginTx() )
{
    Node node = factory.getOrCreate( "name", username );
    tx.success();
    return node;
}

32.11.3. Pessimistic locking for node creation

[Important]Important

While this is a working solution, please consider using the preferred Section 32.11.1, “Get or create unique node using Cypher and unique constraints” instead.

One might be tempted to use Java synchronization for pessimistic locking, but this is dangerous. By mixing locks in Neo4j and in the Java runtime, it is easy to produce deadlocks that are not detectable by Neo4j. As long as all locking is done by Neo4j, all deadlocks will be detected and avoided. Also, a solution using manual synchronization doesn’t ensure uniqueness in an HA environment.

This example uses a single “lock node” for locking. We create it only as a place to put locks, nothing else.

Create a lock node at application start. 

try ( Transaction tx = graphDb.beginTx() )
{
    final Node lockNode = graphDb.createNode();
    tx.success();
    return lockNode;
}

Use the lock node to ensure nodes are not created concurrently. 

try ( Transaction tx = graphDb.beginTx() )
{
    Index<Node> usersIndex = graphDb.index().forNodes( "users" );
    Node userNode = usersIndex.get( "name", username ).getSingle();
    if ( userNode != null )
    {
        return userNode;
    }

    tx.acquireWriteLock( lockNode );
    userNode = usersIndex.get( "name", username ).getSingle();
    if ( userNode == null )
    {
        userNode = graphDb.createNode( DynamicLabel.label( "User" ) );
        usersIndex.add( userNode, "name", username );
        userNode.setProperty( "name", username );
    }
    tx.success();
    return userNode;
}

Note that finishing the transaction will release the lock on the lock node.