DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Build a Java Microservice With AuraDB Free
  • Leveraging Neo4j for Effective Identity Access Management
  • Finally, an ORM That Matches Modern Architectural Patterns!
  • The Beginner's Guide To Understanding Graph Databases

Trending

  • It’s Not About Control — It’s About Collaboration Between Architecture and Security
  • How the Go Runtime Preempts Goroutines for Efficient Concurrency
  • Integration Isn’t a Task — It’s an Architectural Discipline
  • Vibe Coding With GitHub Copilot: Optimizing API Performance in Fintech Microservices
  1. DZone
  2. Data Engineering
  3. Databases
  4. Spring Data Neo4j: How to Update an Entity

Spring Data Neo4j: How to Update an Entity

Several options for updating data with a Spring application. This post walks through some examples using the default save method, custom queries, and projections.

By 
Jennifer Reif user avatar
Jennifer Reif
DZone Core CORE ·
Feb. 10, 25 · Tutorial
Likes (2)
Comment
Save
Tweet
Share
6.9K Views

Join the DZone community and get the full member experience.

Join For Free

After working on a new online Spring Data Neo4j course, I learned a couple more things about updating an entity. The course required a different set of scenarios than outlined in my previous SDN update blog post, so I wanted to cover those scenarios, as well.

Spring save() Method

First up is the out-of-the-box save() method that is provided by Spring as a default. This method takes an input of the entity object you want to save to the database.

Here is an example of what this looks like with a popular movie data set as the model.

MovieDomain Class

Java
 
@Node
public class Movie {
    @Id
    private String movieId;

    private String title;
    private String plot;
    private String poster;
    private String url;
    private String released;

    private Long imdbVotes;

    @Relationship(value = "ACTED_IN", direction = Relationship.Direction.INCOMING)
    private List<Role> actors;
}


MovieController Class

Java
 
@RestController
@RequestMapping("/movies")
public class MovieController {
    //inject repository + constructor

    @PostMapping("/save")
    Movie save(@RequestBody Movie movie) {
        return movieRepo.save(movie);
    }
}


This works well to save a new movie, as you can provide a subset of properties or all properties, as long as the id is present, and anything you don't provide will be set to null. However, the issue arises when you need to update an existing entity because it means any attributes you do not provide will be overwritten to null.

To better understand how this works, let's see it in action by saving a movie, and then trying to update it.

Movie Request Object

JSON
 
{
  "movieId": "9876",
  "title": "MyMovie"
}


Save Movie Object

Shell
 
% http ":8080/movies/save" @src/main/resources/movie.json
{
    "actors": null,
    "imdbId": null,
    "imdbVotes": null,
    "movieId": "9876",
    "plot": null,
    "poster": null,
    "title": "MyMovie",
    "url": null,
    "year": null
}


Now, let's use the same method to try to update the movie with a year property.

Updated Movie Object

JSON
 
{
  "movieId": "9876",
  "year": 2018
}


Save Updated Movie Object

Shell
 
% http ":8080/movies/save" @src/main/resources/movieUpdated.json
{
    "actors": null,
    "imdbId": null,
    "imdbVotes": null,
    "movieId": "9876",
    "plot": null,
    "poster": null,
    "title": null,
    "url": null,
    "year": 2018
}


In the output above, you can see that the title property is null, and the year property is populated. This is because the title is not specified in the updated JSON object, so it is overwritten to null.

This may not be too big of an effort if you have only a few attributes in your domain class, although I would still find it frustrating to include all properties for only updating one or two properties. In that case, I would need to resort to the POJO method outlined in my previous SDN update blog post where you pull the database entity, set each property, and then save the entity. The added business logic seems like an unnecessary maintenance headache.

What if you wanted to preserve what was already there without providing all the properties defined in the domain class to the request? In these scenarios, there are a couple of other options at your disposal, though neither allows dynamic updates to random properties per request.

Patch Year

The first option is that you don't have to set all properties if you use a PATCH method and only set the values you want to update. Here is an example.

MovieController Class

Java
 
@RestController
@RequestMapping("/movies")
public class MovieController {
    //inject repository + constructor

    @PatchMapping("/patchYear")
    Movie patchYear(@RequestBody Movie moviePatch) {
        Movie existingMovie = movieRepository.findById(moviePatch.getMovieId()).get();
        existingMovie.setYear(moviePatch.getYear());

        return movieRepository.save(existingMovie);
    }
}


PatchYear Request Object

JSON
 
{
  "movieId": "9876",
  "year": 2024
}


Patch Movie Year

Shell
 
% http PATCH ":8080/movies/patchYear" @src/main/resources/moviePatch.json
{
    "budget": null,
    "countries": null,
    "imdbId": null,
    "imdbRating": null,
    "imdbVotes": null,
    "languages": null,
    "movieId": "9876",
    "plot": null,
    "poster": null,
    "released": null,
    "revenue": null,
    "runtime": null,
    "title": "MyMovie",
    "tmdbId": null,
    "url": null,
    "year": 2024
}


This allows you to set specific values without overwriting other property values to null. You also don't need to set all the values in the movie object programmatically. If you modified the initial save() method to just include the setYear() line, it would still overwrite other values. This approach prevents that, although you still have to call setProperty() for each field you want to update.

Note: For this approach to work, your domain entity must be a class (not a record) because records are immutable, which means you cannot change (or set/update) fields on the entity instance. For immutable objects, you have to create a new instance of the object and copy property values to the new object before saving.

You can avoid setting each property on an object and retain existing values with a couple of options covered next.

Custom Cypher

One of the more flexible options is to use custom Cypher. For this, you would write a Cypher statement that sets the new values to the properties you want to update. You can even add/set properties that do not exist on the application's domain class. The negative is that you would need to make changes to the application (Cypher statement) if you wanted to update different properties, so it is not fully dynamic.

The example below uses the same movie domain but adds a Cypher statement and method to the repository interface to update the year property without overwriting the title.

MovieRepository Interface

Java
 
interface MovieRepository extends Neo4jRepository<Movie, String> {
    @Query("MATCH (m:Movie {movieId: $movieId}) " +
            "SET m.year = toInteger($year) " +
            "RETURN m;")
    Movie updateYear(String movieId, Long year);
}


MovieController Class

Java
 
@RestController
@RequestMapping("/movies")
public class MovieController {
    //inject repository + constructor

    @PatchMapping("/updateYear")
    Movie patchYear(@RequestParam String movieId, @RequestParam Long year) {
        return movieRepository.updateYear(movieId, year);
    }
}


Then, the following request calls the method and updates the movie's year property.

Update Movie Year

Shell
 
% http PATCH ":8080/movies/updateYear?movieId=9876&year=2018"
{
    "actors": [],
    "imdbId": null,
    "imdbVotes": null,
    "movieId": "9876",
    "plot": null,
    "poster": null,
    "title": "MyMovie",
    "url": null,
    "year": 2018
}


It worked! The movie's title remained the same (not overwritten to null), and a value was saved for the year property.

This ad hoc Cypher approach could work well when values or property updates occur somewhat frequently, as updating the Cypher statement makes updates flexible. You could also make the incoming property generic (value) and pass in any value (or multiple values) and set whichever properties you'd like by changing the Cypher. While still not completely dynamic, this option is probably the most flexible and dynamic of the list.

A custom Cypher approach might work well when you need to update certain properties, but if you have a subset of properties that operate together, another option is to create a projection of the domain class.

Projections

To provide a consistent set of values for updates and leave other properties as-is, projections are probably the nicest option I've found so far. This approach still requires setting consistent properties (like with custom Cypher) but avoids overwriting consistent values by creating a "view" of the larger entity, only working with those values and leaving other field values alone.

Note: There are two types of projections — interface and DTO. Interface projections are immutable, which means you cannot update values, but have to create a new object and copy existing values over. DTO objects, then, are more straightforward when dealing with update operations. For this reason, the examples use DTO-based projections.

There are two different ways to save a projection — 1. send the projection (a subset of properties) and save it into the full domain entity; 2. send a full domain object but only save the projection fields. Really, the difference is the incoming request object, whether you have a smaller set or larger set and only want to save those values.

Let's see how this operates.

Projection as Movie

The first example sends a projection object (subset of the full domain object's properties) and saves the trimmed object as the full movie entity. We have defined a projection that only includes the movieId and plot properties of a movie.

MovieDTOProjection Class

Java
 
public class MovieDTOProjection {
    private String movieId;
    private String plot;
  
    public String getMovieId() { return movieId; }
    public String getPlot() { return plot; }
    public void setPlot(String plot) { this.plot = plot; }
}


MovieController Class Method

Java
 
@RestController
@RequestMapping("/movies")
public class MovieController {
    //inject repository + constructor

    //Only updates properties in projection
    @PatchMapping("/projectionAsMovie")
    MovieDTOProjection saveProjectionAsMovie(@RequestBody MovieDTOProjection movieDTO) {
        return neo4jTemplate.save(Movie.class).one(movieDTO);
    }
}


ProjectionAsMovie Object (Request Object)

JSON
 
{
  "movieId": "9876",
  "plot": "Here is the plot."
}


Update Movie With a Projection Object

Shell
 
% http PATCH ":8080/movies/projectionAsMovie" @src/main/resources/projectionAsMovie.json
{
    "movieId": "9876",
    "plot": "Here is the plot."
}


Full Database Entity

JSON
 
{
    "plot": "Here is the plot.",
    "year": 2024,
    "movieId": "9876",
    "title": "MyMovie"
}


The request successfully updated the entity with the new plot value ("Here is the plot.") without overwriting the title or year properties on the existing entity! The method in the controller class takes the projection object input and saves it as a Movie class entity.

This approach is helpful when you only want to send certain properties over the wire and not the full object. You can set any fields you wish to update in the projection and only send those values in the request.

Movie Entity as Projection

This type of projection only saves a defined subset of properties out of a potentially larger or variable object. The save will only update the fields included in the projection and ignore anything else that might be in the request object.

The example below uses the same MovieDTOProjection class as the above example. Then, we only need a new method in the controller to save the projection.

MovieController Class Method

Java
 
@RestController
@RequestMapping("/movies")
public class MovieController {
    //inject repository + constructor

    //Only updates properties in projection (ignores other values)
    @PatchMapping("/movieAsProjection")
    MovieDTOProjection saveMovieAsProjection(@RequestBody Movie movie) {
        return neo4jTemplate.saveAs(movie, MovieDTOProjection.class);
    }
}


MovieAsProjection Object

JSON
 
{
  "movieId": "9876",
  "title": "TestTitle",
  "plot": "Some plot cliche here.",
  "year": 2025
}


Send Movie Object (Only Save Projection Values)

Shell
 
% http PATCH ":8080/movies/movieAsProjection" @src/main/resources/movieAsProjection.json
{
    "movieId": "9876",
    "plot": "Some plot cliche here."
}


Full Database Entity

JSON
 
{
    "plot": "Some plot cliche here.",
    "year": 2024,
    "movieId": "9876",
    "title": "MyMovie"
}


This also worked! The controller method accepts a Movie request object as input and saves it as the projection entity, which only retains the subset of values defined in the projection and ignores the rest.

This approach would be helpful if you have request objects with varying fields of a Movie entity, but only want to update a certain subset each time. In terms of the incoming request object, this option seems to be the most flexible in allowing you to provide all kinds of fields as input, and it will only save the relevant ones defined in the projection.

Wrapping Up!

In this post, we delved a bit deeper into updating entities using Spring Data and Neo4j. A previous blog post on the topic outlined some examples for that use case at the time (microservices), but I learned about a few more options after working on the GraphAcademy Spring Data Neo4j course. These options included custom Cypher statements and projections.

There are still other pieces of the puzzle to explore and integrate, such as optimistic locking (with the @Version field) and custom repository implementations for extending repositories (e.g., to combine Neo4jRepository<>, Neo4jTemplate, and/or Neo4jClient levels of abstraction), but we will save those for a future blog post. Happy coding!

Resources

  • Code: sdn-update-entity-round2
  • Blog post: previous SDN update article
  • Graphacademy course (free, online, self-paced): Spring Data Neo4j
  • Documentation: SDN projection persistence
  • Documentation: SDN custom repository implementations
Database Neo4j Spring Data

Opinions expressed by DZone contributors are their own.

Related

  • Build a Java Microservice With AuraDB Free
  • Leveraging Neo4j for Effective Identity Access Management
  • Finally, an ORM That Matches Modern Architectural Patterns!
  • The Beginner's Guide To Understanding Graph Databases

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!