Unique nodes

This describes how to ensure the uniqueness of a property when creating nodes.

Creating unique nodes

In many use cases, a certain level of uniqueness is desired among entities. For example, only one user with a certain email address may exist in a system. If multiple concurrent threads naively try to create the user, duplicates will be created.

The following are the main strategies for ensuring uniqueness, and they all work across cluster and single-instance deployments.

Single thread

By using a single thread, no two threads even try to create a particular entity simultaneously. In a cluster, an external single-threaded client can perform the operations.

Get or create

Defining a uniqueness constraint and using the Cypher MERGE clause is the most efficient way to get or create a unique node.

The source code for the examples can be found in: GetOrCreateDocIT.java

Create a unique constraint:

try ( Transaction tx = graphdb.beginTx() )
{
    tx.schema()
        .constraintFor( Label.label( "User" ) )
        .assertPropertyIsUnique( "name" )
        .withName( "usernames" )
        .create();
    tx.commit();
}

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 = tx.execute( queryString, parameters ).columnAs( "n" );
    result = resultIterator.next();
    tx.commit();
    return result;
}

The MERGE clause takes either a read-lock on the matching node that already exists or a write-lock and ensures that the current transaction is the only one creating the node.

It is technically possible to use a lock node or a lock property but this should be avoided if possible. Using the lock node pattern is difficult to do correctly and it has worse performance characteristics because it always involves a write-lock.

You might also be tempted to use Java synchronization for pessimistic locking, but this is dangerous. By mixing locks in Neo4j and the Java runtime, it is possible 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.

For more information on locks and deadlocks, see Operations Manual → Locks and deadlocks.