12.6. Creating unique nodes

12.6.1. Single thread
12.6.2. Get or create
12.6.3. Pessimistic locking

In many use cases, a certain level of uniqueness is desired among entities. You could for instance imagine that only one user with a certain e-mail address may exist in a system. If multiple concurrent threads naively try to create the user, duplicates will be created. There are three main strategies for ensuring uniqueness, and they all work across HA and single-instance deployments.

12.6.1. Single thread

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

12.6.2. Get or create

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 guaranteed 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.

Example code:

public Node getOrCreateUserWithUniqueFactory( String username, GraphDatabaseService graphDb )
{
    UniqueFactory<Node> factory = new UniqueFactory.UniqueNodeFactory( graphDb, "users" )
    {
        @Override
        protected void initialize( Node created, Map<String, Object> properties )
        {
            created.setProperty( "name", properties.get( "name" ) );
        }
    };

    return factory.getOrCreate( "name", username );
}

12.6.3. Pessimistic locking

[Important]Important

While this is a working solution, please consider using the preferred Section 12.6.2, “Get or create” instead.

By using explicit, pessimistic locking, unique creation of entities can be achieved in a multi-threaded environment. It is most commonly done by locking on a single or a set of common nodes.

One might be tempted to use Java synchronization for this, but it is dangerous. By mixing locks in the Neo4j kernel 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.

Example code:

public Node getOrCreateUserPessimistically( String username, GraphDatabaseService graphDb, Node lockNode )
{
    Index<Node> usersIndex = graphDb.index().forNodes( "users" );
    Node userNode = usersIndex.get( "name", username ).getSingle();
    if ( userNode != null ) return userNode;
    Transaction tx = graphDb.beginTx();
    try
    {
        tx.acquireWriteLock( lockNode );
        userNode = usersIndex.get( "name", username ).getSingle();
        if ( userNode == null )
        {
            userNode = graphDb.createNode();
            userNode.setProperty( "name", username );
            usersIndex.add( userNode, "name", username );
        }
        tx.success();
        return userNode;
    }
    finally
    {
        tx.finish();
    }
}