Enterprise Java

Pouring Coffee into the Matrix: Building Java Applications on Neo4j

There always seems to be a collection of content around a single, specific technology, but it’s more difficult to find content for using more than one technology together. In this blog post, we will step through building an application in Java that highlights the capabilities and powers of what a graph database can help developers achieve.

Let’s code!

We will need a few things set up before we write our Java code. The first thing is a Neo4j database. There are a few options to do this, but the easiest is to start a free instance of Neo4j AuraDB, which is a database-as-a-service. You just need to sign up for an account at console.neo4j.io and step through the prompts to create and start an instance (note: choose the option for an empty instance on the data set step and be sure to either download the credentials file or save the credentials somewhere you can reference them).

Next, we need to load the data for Java versions into the database. To do this, once your database instance is up and running, click the Query button on the right side of the panel and copy/paste the Cypher statements from the graph-demo-datasets Github repository.

Once the data has finished loading, we can verify the data model by running the following query in the query editor command line.

CALL apoc.meta.graph();
Data model - JavaVersion to VersionDiffs

Next, we need to set up our application project template. There are many ways to create a Java project, but we will utilize Spring Initializr to create a Spring Boot application.

Spring Initializr - creating the project

The Spring Initializr is found at start.spring.io. We’re going to choose Maven for the project type today, then Java for the language. We can leave the defaults for the rest of the fields in that column.

In the right column, we will choose what dependencies for the project. Click on the Add Dependencies button, then type in and click on each one. We will need the Neo4j and the Spring Web dependencies. Finally, while not required, I have chosen to use Lombok for this project, which cuts down on boilerplate through annotations.

Now we can generate the project with the Generate button at the bottom, which will download the project to our machine as a zip file. Then we can unzip it and open it in an IDE.

All the code written for today’s demo is available in the related Github repository (branches step-one and step-two). The first bit of code we need to write are a few properties to connect to our Neo4j database. Let’s open the application.properties file and fill in some details.

spring.neo4j.uri=<NEO4J_URI>
spring.neo4j.authentication.username=<NEO4J_USERNAME>
spring.neo4j.authentication.password=<NEO4J_PASSWORD>
spring.data.neo4j.database=<NEO4J_DB>

In the place of the non-meaningful values on the right side of the equals signs, copy/paste in the credentials from your specific Neo4j AuraDB instance. Those can be found in the environment file you downloaded when you created the instance, with exception of the database. Unless you have made a conscious effort to switch from the default database, you can put neo4j as the value for that property.

Now we can start coding our data domain into the application, starting with the JavaVersion entity.

@Data
@Node
public class JavaVersion {
    @Id
    @Property("version")
    private final String javaVersion;
    private String name, codeName, status, apiSpec;
    private LocalDate gaDate, eolDate;
}

The first line uses the Lombok annotation of @Data, which will generate getters, setters, equals, hashcode, and toString methods, as well as a constructor with required arguments for the class. The @Node annotation tells the application that this entity maps to a database entity. Since we are working with Neo4j, and its domain model uses nodes and relationships for entities, we need to look for nodes in the database containing these details.

Within the class, we can specify a few fields as properties. Our first property is the javaVersion, which is the unique id field for the entity. We can notate that with the @Id annotation, and also use the @Property annotation to map a mismatch in name between the database and application. The database contains the property named version for the Java version; however, version is a reserved keyword in Java, so instead, we used javaVersion in the application.

The last two lines contain additional properties for name, code name, status, API specification, general availability date, and end-of-life date.

Next, we need to add a repository interface that allows us to call methods to interact with data in the database.

public interface JavaVersionRepo extends Neo4jRepository<JavaVersion, String> {
}

This repository extends the Neo4jRepository<> interface and works with the JavaVersion domain type, which has an Id type of String. We don’t need to define any methods because we can utilize Spring’s derived methods it provides out-of-the-box. Implementations of certain methods such as findAll(), findById(), etc often look the same for many developers, so Spring provides standard implementations for these. As always, developers can customize or write their own methods for special cases and needs.

Next, let’s define our controller class to set up a REST endpoint for us to call and retrieve data from Neo4j.

@RestController
@RequestMapping("/versions")
@AllArgsConstructor
public class JavaVersionController {
    private final JavaVersionRepo javaVersionRepo;

    @GetMapping
    Iterable<JavaVersion> findAllJavaVersions() { return javaVersionRepo.findAll(); }
}

This class has three annotations – @RestController, @RequestMapping(), and @AllArgsConstructor. @RestController tells Spring this will be our rest controller class, and the @RequestMapping() sets up our endpoint (in our case, /versions) for us to call in order to access the methods defined in this class. The final annotation of @AllArgsConstructor is a Lombok annotation that creates a constructor with all arguments. That gets used when we inject our repository interface in the first line inside the controller class.

Inside the class definition, we inject our repository, and then define a method for retrieving the Java versions from the database. The @GetMapping annotation is a shortcut annotation that specifies a request mapping for a GET method. The subsequent line defines that method. We expect multiple Java version entities to return, so we set up a return type of Iterable<JavaVersion> and name our method findAllJavaVersions(). Inside the method, we return the results of calling the Spring-derived findAll() method from the repository interface (repo).

This creates a starting point where we can test retrieving Java versions from Neo4j. Let’s run the application! Once started via the IDE or command line, we can navigate to a web browser with the URL localhost:8080/versions to see our data.

Test application - Java version entities

At this point, we haven’t taken advantage of the graph because we aren’t using relationships between entities (only retrieving JavaVersion entities). Let’s head back to our code and fix that!

Adding relationships

We can’t have a relationship without a node on the other end, so we need to add our VersionDiff nodes from our database model to our application model by creating another data class.

@Node
@Data
public class VersionDiff {
    @Id
    @GeneratedValue
    private final Long neoId;
    private String fromVendor, fromVersion, toVendor, toVersion;
}

The annotations on this class are the same as our JavaVersion class, so we can jump inside the curly braces. We are using the @Id annotation to mark this variable as the unique identifier for the class, and also using the @GeneratedValue annotation because this field is not a user-provided value, but a system-generated one. The field is the internal Neo4j id because this class doesn’t have a single, clear business key. The last line lists the rest of the fields – which vendor and version we are starting with for the compare, and which vendor and version we are ending with for the compare.

Now we need to connect the JavaVersion and VersionDiff application entities together with a relationship. To do that, we need to go back to the JavaVersion domain class.

@Node
@Data
public class JavaVersion {
    <entity fields>

    @Relationship("FROM_NEWER")
    private List<VersionDiff> olderVersionDiffs;

    @Relationship("FROM_OLDER")
    private List<VersionDiff> newerVersionDiffs;
}

There are two @Relationship annotations added. The first one is comparing a JavaVersion with any previous versions released (i.e. Java 11 compared to 9, 8, 6, etc). We use the @Relationship annotation to specify this field maps crossing over a relationship entity in the database in order to pull the VersionDiff entities on the other side. The second @Relationship annotation is looking for version diffs that are newer than the version we are starting with (i.e. Java 11 compared to 14, 17, 19, etc).

Mapping both of these relationships matches our data model image we saw earlier, where there are two kinds of relationships between JavaVersion entities and VersionDiff entities for comparing older and newer version releases of the language.

Let’s test our changes! Restart the application and navigate/refresh our browser to localhost:8080/versions.

Test application - Java versions and diffs

We can see the related entities returning, although our data set does not include diffs for Java version 1.0, so that’s why we see empty lists in the Java 1.0 results and why we don’t see older versions in the Java 1.1 results.

Wrap Up!

In today’s post, we saw how graphs and Java can work together by building a Java application that connected to a Neo4j graph database. We saw the capabilities of the graph and how the data model between database and application are refreshingly similar.

There are so many more things we will explore in the future, such as additional features for creating custom queries and domain class projections. Until then, happy coding!

Resources

Jennifer Reif

Jennifer Reif is a Developer Advocate at Neo4j, speaker, and blogger with an MS in CMIS. An avid developer and problem-solver, her passion is finding ways to organize chaos and deliver software more effectively.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button