3.13. Creating unique nodes

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

For an overview of unique nodes, see Section 5.6, “Creating unique nodes”.

3.13.1. Getting or creating a unique node using Cypher and unique constraints

Create a unique constraint:

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

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

3.13.2. Getting or creating a unique node using an explicit index

While this is a working solution, please consider using the preferred solution at Section 3.13.1, “Getting or creating a 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( Label.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;
        }

3.13.3. Pessimistic locking for node creation

While this is a working solution, please consider using the preferred solution at Section 3.13.1, “Getting or creating a 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 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. Also, a solution using manual synchronization does not 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( Label.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.