Enterprise Java

Java Application with Neo4j: How to use Spring custom queries and projections

In the previous blog post, we built a Java application on top of a Neo4j graph database showcasing the minimal translation between database and application models and the benefits of storing data with relationships.

We can take this a few steps further by exploring some additional features offered between the application and database, such as custom queries and domain class projections.

Project recap

Last time, we built a Spring Boot application that connected to a Neo4j AuraDB free cloud instance containing Java version data. The data model in the database shows two nodes connected by two different types of relationships. One relationship pulls diffs with older language versions, and one relationship pulls diffs with newer language versions.

Graph data model - Java version entities connected to diff entities
Graph data model – Java version entities connected to diff entities

In our application code, we created a REST API that called the derived Spring findAll() method to retrieve Java versions and connected version diffs from the database. We can review the current status of the application by pulling the step-two branch of the Github repository, running the application from an IDE or command line, and navigating a browser to the URL localhost:8080/versions to see the data.

Test application - Java versions and diffs
Test application – Java versions and diffs

Now that we have reviewed where we left off, let’s dive into a couple of other special features with Java and Spring Data Neo4j. As always, the code written for today’s demo is available in the related Github repository (step-three and main branches).

Custom queries

We can write our own custom queries instead of using Spring’s provided derived methods. We do this by writing our own method and accompanying query in the repository interface. Let’s say we want to be a bit more selective about the data we get back in a custom query. We only want to retrieve JavaVersion entities that have a FROM_NEWER relationship, ignoring all the connected diffs to newer versions. This means our results will exclude Java 1.0 because it doesn’t have any diffs connected to it at all. They will also exclude Java 1.1 because it doesn’t have any older versions connected to it.

Add custom query to repository

public interface JavaVersionRepo extends Neo4jRepository<JavaVersion, String> {
    @Query("MATCH (j:JavaVersion)-[r:FROM_NEWER]->(d:VersionDiff) RETURN j, collect(r), collect(d);")
    Iterable<JavaVersion> findAllCustom();
}

We use the @Query annotation to specify our domain-specific language query. Because Neo4j uses Cypher for its query language, we write a Cypher query that matches JavaVersion nodes connected to VersionDiff nodes across a FROM_NEWER relationship and returns each JavaVersion entity and collected relationship and diff lists. The next line defines our findAllCustom() method that expects multiple JavaVersion entities in the results.

Next, we need to add the method to our controller class.

Add custom query to controller

@RestController
@RequestMapping("/versions")
@AllArgsConstructor
public class JavaVersionController {
    <repository injection>
    <findAll default method>

    @GetMapping("/custom")
    Iterable<JavaVersion> findAllCustom() { return javaVersionRepo.findAllCustom(); }
}

We will create a separate endpoint/implementation for this method, so that we can compare existing functionality. The @GetMapping annotation appears again, but we have added a /custom to the url for this method. We then define the method with our return type and method name, and return the results from calling the repository’s findAllCustom() method we just created.

Test custom query

Let’s test it in our browser at localhost:8080/versions/custom!

Test application - Java versions and older diffs with custom query
Test application – Java versions and older diffs with custom query

Everything is as expected! We don’t see Java 1.0 or 1.1 in the list because they don’t have any FROM_NEWER relationships. We also don’t see any results in the newerVersionDiffs because we are ignoring the FROM_OLDER relationships in our custom query.

Let’s see if we can be even more selective about the data we return by using a projection.

Projection interfaces

We can use projections like a custom view of a data class, where we can trim out some fields or manipulate certain fields. Let’s try this with our JavaVersion class so that we only see the version and the list of versions for the compared diffs. To do that, we will need to create a new interface called JavaVersionProjection.

Create projection interface

public interface JavaVersionProjection {
    String getJavaVersion();
    List<DiffProjection> getOlderVersionDiffs();

    interface DiffProjection {
        String getFromVersion();
    }
}

Inside the interface definition, we can utilize getters from domain classes to retrieve desired pieces of our larger data classes. We start by calling the getJavaVersion() to retrieve the javaVersion property from our JavaVersion class.

Then, we want to pull a comparing version from the VersionDiff entity. So we need to create a nested projection to avoid pulling all the values from VersionDiff. We expect a List<> of VersionDiff entities to return, then call the getter method from the JavaVersion domain class for getOlderVersionDiffs(). The next line of code defines the DiffProjection as a nested projection within our JavaVersionProjection. Inside the DiffProjection definition, we call the getFromVersion() method to retrieve the field of the other Java version we are comparing.

With this in place, we can plug our JavaVersionProjection into repository and controller methods. Let’s start with the repository.

Use projection in repository query

public interface JavaVersionRepo extends Neo4jRepository<JavaVersion, String> {
    <findAllCustom() method>

    @Query("MATCH (j:JavaVersion)-[r:FROM_NEWER]->(d:VersionDiff) RETURN j, collect(r), collect(d);")
    Iterable<JavaVersionProjection> findAllProjection();
}

We will copy/paste our custom query to a new query for our projection. The only differences are the name of the method (called this one findAllProjection()) and the return type (Iterable<JavaVersionProjection>).

Next, we need to add it to our controller class.

Use projection in controller

@RestController
@RequestMapping("/versions")
@AllArgsConstructor
public class JavaVersionController {
    <repository injection>
    <findAll default method>
    <findAll custom method>

    @GetMapping("/projection")
    Iterable<JavaVersionProjection> findAllProjections() { return javaVersionRepo.findAllProjections(); }
}

Similar to our repository, we will create a new endpoint for comparison with the old results. We use the /projection added to the url for our @GetMapping, then have a return type of multiple JavaVersionProjection entities and name the method findAllProjections(). Inside the method, we return the results from calling the repository’s findAllProjections() method.

Test projection

Let’s test it by going to our browser with the URL localhost:8080/versions/projection!

Test application - Java projection of versions and diffs
Test application – Java projection of versions and diffs

We are getting back only the Java version and the list of versions from compared diffs, making the results clean and easy to read. We can also test our other endpoints of localhost:8080/versions and localhost:8080/custom to see what we had previously.

Wrap up!

In today’s post, we continued some previous work on a Java application. We explored additional features for Java applications with custom queries and projections, allowing us to pick and choose the data returning.

There are so many more things we could explore on this topic, but hopefully, this sparked your curiosity to discover. 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.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
play snake
6 months ago

I was browsing the internet a few months back when I came across another website that provided an in-depth discussion on this subject. I am relieved that you were able to put some light on the true nature of the situation that is taking on out there. Certain websites have a clear and obvious bias toward topics of this nature. In light of this, what do you anticipate will happen to the industry as a whole?

Back to top button