1. Introduction
This article is about Neo4j – one of the most mature and full-featured graph databases on the market today. Graph databases approach the task of data modeling with the view that many things in life lend themselves to being represented as a collection of nodes (V) and connections between them called edges (E).
2. Embedded Neo4j
The easiest way to get started with Neo4j is to use the embedded version in which Neo4j runs in the same JVM as your application.
First, we need to add a Maven dependency:
<dependency> <groupId>org.neo4j</groupId> <artifactId>neo4j</artifactId> <version>3.1.0</version> </dependency>
You can check this link to download the latest version.
Next, let’s create a factory:
GraphDatabaseFactory graphDbFactory = new GraphDatabaseFactory();
Finally, we create an embedded database:
GraphDatabaseService graphDb = graphDbFactory.newEmbeddedDatabase( new File("data/cars"));
Now the real action can begin! First, we need to create some nodes in our graph and for that, we need to start a transaction since Neo4j will reject any destructive operation unless a transaction has been started:
graphDb.beginTx();
Once we have a transaction in progress, we can start adding nodes:
Node car = graphDb.createNode(Label.label("Car")); car.setProperty("make", "tesla"); car.setProperty("model", "model3"); Node owner = graphDb.createNode(Label.label("Person")); owner.setProperty("firstName", "baeldung"); owner.setProperty("lastName", "baeldung");
Here we added a node Car with properties make and model as well as node Person with properties firstName and lastName
Now we can add a relationship:
owner.createRelationshipTo(car, RelationshipType.withName("owner"));
The statement above added an edge joining the two nodes with an owner label. We can verify this relationship by running a query written in Neo4j’s powerful Cypher language:
Result result = graphDb.execute( "MATCH (c:Car) <-[owner]- (p:Person) " + "WHERE c.make = 'tesla'" + "RETURN p.firstName, p.lastName");
Here we ask to find a car owner for any car whose make is tesla and give us back his/her firstName and lastName. Unsurprisingly, this returns: {p.firstName=baeldung, p.lastName=baeldung}
3. Cypher Query Language
Neo4j provides a very powerful and pretty intuitive querying language which supports the full range of features one would expect from a database. Let us examine how we can accomplish that standard create, retrieve, update and delete tasks.
3.1. Create Node
Create keyword can be used to create both nodes and relationships.
CREATE (self:Company {name:"Baeldung"}) RETURN self
Here we’ve created a company with a single property name. A node definition is marked by parentheses and its properties are enclosed in curly braces. In this case, self is an alias for the node and Company is a node label.
3.2. Create Relationship
It is possible to create a node and a relationship to that node all in a single query:
Result result = graphDb.execute( "CREATE (baeldung:Company {name:\"Baeldung\"}) " + "-[:owns]-> (tesla:Car {make: 'tesla', model: 'modelX'})" + "RETURN baeldung, tesla");
Here we’ve created nodes baeldung and tesla and established an ownership relationship between them. Creating relationships to pre-existing nodes is, of course, also possible.
3.3. Retrieve Data
MATCH keyword is used to find data in combination with RETURN to control which data points are returned. The WHERE clause can be utilized to filter out only those nodes that have the properties we desire.
Let us figure out the name of the company that owns tesla modelX:
Result result = graphDb.execute( "MATCH (company:Company)-[:owns]-> (car:Car)" + "WHERE car.make='tesla' and car.model='modelX'" + "RETURN company.name");
3.4. Update Nodes
SET keyword can be used for updating node properties or labels. Let us add mileage to our tesla:
Result result = graphDb.execute("MATCH (car:Car)" + "WHERE car.make='tesla'" + " SET car.milage=120" + " SET car :Car:Electro" + " SET car.model=NULL" + " RETURN car");
Here we add a new property called milage, modify labels to be both Car and Electro and finally, we delete model property altogether.
3.5. Delete Nodes
DELETE keyword can be used for permanent removal of nodes or relationships from the graph:
graphDb.execute("MATCH (company:Company)" + " WHERE company.name='Baeldung'" + " DELETE company");
Here we deleted a company named Baeldung.
3.6. Parameter Binding
In the above examples, we have hard-coded parameter values which isn’t the best practice. Luckily, Neo4j provides a facility for binding variables to a query:
Map<String, Object> params = new HashMap<>(); params.put("name", "baeldung"); params.put("make", "tesla"); params.put("model", "modelS"); Result result = graphDb.execute("CREATE (baeldung:Company {name:$name}) " + "-[:owns]-> (tesla:Car {make: $make, model: $model})" + "RETURN baeldung, tesla", params);
4. Java Driver
So far we’ve been looking at interacting with an embedded Neo4j instance, however, in all probability for production, we’d want to run a stand-alone server and connect to it via a provided driver. First, we need to add another dependency in our maven pom.xml:
<dependency> <groupId>org.neo4j.driver</groupId> <artifactId>neo4j-java-driver</artifactId> <version>1.1.1</version> </dependency>
You can follow this link to check for the latest version of this driver.
Now we can establish a connection:
Driver driver = GraphDatabase.driver( "bolt://localhost:7687", AuthTokens.basic("neo4j", "12345"));
Then, create a session:
Session session = driver.session();
Finally, we can run some queries:
session.run("CREATE (baeldung:Company {name:\"Baeldung\"}) " + "-[:owns]-> (tesla:Car {make: 'tesla', model: 'modelX'})" + "RETURN baeldung, tesla");
Once we are done with all our work we need to close both session and the driver:
session.close(); driver.close();
5. JDBC Driver
It is also possible to interact with Neo4j via a JDBC driver. Yet another dependency for our pom.xml:
<dependency> <groupId>org.neo4j</groupId> <artifactId>neo4j-jdbc-driver</artifactId> <version>3.0.1</version> </dependency>
You can follow this link to download the latest version of this driver.
Next, let’s establish a JDBC connection:
Connection con = DriverManager.getConnection( "jdbc:neo4j:bolt://localhost/?user=neo4j,password=12345,scheme=basic");
Here con is a regular JDBC connection which can be used for creating and executing statements or prepared statements:
try (Statement stmt = con. stmt.execute("CREATE (baeldung:Company {name:\"Baeldung\"}) " + "-[:owns]-> (tesla:Car {make: 'tesla', model: 'modelX'})" + "RETURN baeldung, tesla") ResultSet rs = stmt.executeQuery( "MATCH (company:Company)-[:owns]-> (car:Car)" + "WHERE car.make='tesla' and car.model='modelX'" + "RETURN company.name"); while (rs.next()) { rs.getString("company.name"); } }
6. Object-Graph-Mapping
Object-Graph-Mapping or OGM is a technique which enables us to use our domain POJOs as entities in the Neo4j database. Let us examine how this works. The first step, as usually, we add new dependencies to our pom.xml:
<dependency> <groupId>org.neo4j</groupId> <artifactId>neo4j-ogm-core</artifactId> <version>2.1.1</version> </dependency> <dependency> <groupId>org.neo4j</groupId> <artifactId>neo4j-ogm-embedded-driver</artifactId> <version>2.1.1</version> </dependency>
You can check the OGM Core Link and OGM Embedded Driver Link to check for the latest versions of these libraries.
Second, we annotate our POJO’s with OGM annotations:
@NodeEntity public class Company { private Long id; private String name; @Relationship(type="owns") private Car car; } @NodeEntity public class Car { private Long id; private String make; @Relationship(direction = "INCOMING") private Company company; }
@NodeEntity informs Neo4j that this object will need to be represented by a node in the resulting graph. @Relationship communicates the need to create a relationship with a node representing the related type. In this case, a company owns a car.
Please note that Neo4j requires each entity to have a primary key, with a field named id being picked up by default. An alternatively named field could be used by annotating it with @GraphId.
Then, we need to create a configuration that will be used to bootstrap Neo4j‘s OGM. For simplicity, let us use an embedded in-memory only database:
Configuration conf = new Configuration(); conf.driverConfiguration().setDriverClassName( "org.neo4j.ogm.drivers.embedded.driver.EmbeddedDriver");
After that, we initialize SessionFactory with the configuration we created and a package name in which our annotated POJO’s reside:
SessionFactory factory = new SessionFactory(conf, "com.baeldung.graph");
Finally, we can create a Session and begin using it:
Session session = factory.openSession(); Car tesla = new Car("tesla", "modelS"); Company baeldung = new Company("baeldung"); baeldung.setCar(tesla); session.save(baeldung);
Here we initiated a session, created our POJO’s and asked OGM session to persist them. Neo4j OGM runtime transparently converted objects to a set of Cypher queries which created appropriate nodes and edges in the database.
If this process seems familiar, that is because it is! That is precisely how JPA works, the only difference being whether object gets translated into rows that are persisted to an RDBMS, or a series of nodes and edges persisted to a graph database.
7. Conclusion
This article looked at some basics of a graph-oriented database Neo4j.
As always, the code in this write-up is all available over on Github.