Update: Accessing Neo4j with Spring Boot 2.4

In Spring Boot 2.3, if you want to use Spring reactive stack with Neo4j database — the well-known Graph NoSQL database, you should have to choose the work from Neo4j team.

I had written a post before to describe this project. In this and the next posts I will update to the newest Spring Boot 2.4/Spring Data Neo4j 6.0 GA.

  • Update: Accessing Neo4j with Spring Boot 2.4(you are here)

All source codes can be found from my Github.

The effort of Spring Data Neo4j RX has been merged into the official Spring Data Neo4j project, and we will have new updated reactive support in the final Spring Data Neo4j 6.0 GA and the upcoming Spring Boot 2.4 release.

In this post, we will recreate our blog post example application with the newest Spring Boot 2.4/Spring Data Neo4j 6.0, and we will also cover some points that are useful for those people who are migrating from Spring Data Neo4j RX .

Firstly, open your favorite browser and navigate to http://start.spring.io , and generate a Spring WebFlux project skeleton using Spring initializr.

  • Choose Maven as project type(If you prefer Gradle, choose Gradle please)
  • And select Spring Boot 2.4.0-RC1
  • And select Java 11 or Java 15, personally I would like use the latest Java to experience the upcoming preview features
  • Add the following dependencies.
  • Data Neo4j
  • Web Reactive
  • Lombok

Extract the downloaded archive into your disc, and import into your IDE, such as IntelliJ IDEA.

Open the pom.xml file in the project root, you will see the following dependencies added in the dependencies section.

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-neo4j</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

Now , create a Noe4j entity class as the following.

@Node
@Data
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
class Post {
@Id
@GeneratedValue
private Long id;
private String title;
private String content;
}

A Neo4j entity is annotated with a @Node annotation.

Create a Repository for the Post entity.

interface PostRepository extends ReactiveNeo4jRepository<Post, Long> {
}

Create a RestController to expose the simple CRUD RESTful APIs for the Post entity.

@RestController()
@RequestMapping(value = "/posts")
@RequiredArgsConstructor
class PostController {
private final PostRepository posts; @GetMapping("")
public Flux<Post> all() {
return this.posts.findAll();
}
@PostMapping("")
public Mono<Post> create(@RequestBody Post post) {
return this.posts.save(post);
}
@GetMapping("/{id}")
public Mono<Post> get(@PathVariable("id") Long id) {
return Mono.just(id)
.flatMap(posts::findById)
.switchIfEmpty(Mono.error(new PostNotFoundException(id)));
}
@PutMapping("/{id}")
public Mono<Post> update(@PathVariable("id") Long id, @RequestBody Post post) {
return this.posts.findById(id)
.map(p -> {
p.setTitle(post.getTitle());
p.setContent(post.getContent());
return p;
})
.flatMap(this.posts::save);
}
@DeleteMapping("/{id}")
public Mono<Void> delete(@PathVariable("id") Long id) {
return this.posts.deleteById(id);
}
}

Let’s have a look at this controller, it is very similar to the general imperative version in Spring MVC, but here it returns a reactive specific Mono or Flux in these methods.

In the above get method, when the post is not found it will throw a PostNotFoundException. Create a @RestControllerAdvice annotated class to handle this exception.

@RestControllerAdvice
@Slf4j
class RestExceptionHandler {
@ExceptionHandler(PostNotFoundException.class)
ResponseEntity postNotFound(PostNotFoundException ex) {
log.debug("handling exception::" + ex);
return notFound().build();
}
}

Add a ReactiveTransactionManager bean. In the Spring Data Neo4j 6.0, it seems activating a reactive transaction manager becomes a must, if it is not set, you will see an exception thrown at the startup stage when running the application.

// see: https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.4.0-M2-Release-Notes#neo4j-1
@Bean(ReactiveNeo4jRepositoryConfigurationExtension.DEFAULT_TRANSACTION_MANAGER_BEAN_NAME)
public ReactiveTransactionManager reactiveTransactionManager(
Driver driver,
ReactiveDatabaseSelectionProvider databaseNameProvider) {
return new ReactiveNeo4jTransactionManager(driver, databaseNameProvider);
}

Use a CommandLineRunner bean to initialize some sample data. Here we use PostRepository to insert two Post sample data.

@Component
@Slf4j
@RequiredArgsConstructor
class DataInitializer implements CommandLineRunner {
private final PostRepository posts; @Override
public void run(String[] args) {
log.info("start data initialization...");
this.posts.deleteAll()
.thenMany(
Flux
.just("Post one", "Post two")
.flatMap(
title -> this.posts.save(Post.builder().title(title).content("The content of " + title).build())
)
)
.log()
.thenMany(
this.posts.findAll()
)
.log("[Initializing data]")
.subscribe(
data -> log.info("found post: {}", data),
err -> log.error("error", err),
() -> log.info("done")
);
}}

Before starting this application, make sure there is a running Neo4j server.

There is a docker-compose.yaml file in the root folder of the spring-reactive-sample repository which is prepared for bootstrapping dependent servers.

Simply, run the following command to serve a Neo4j instance in the Docker container.

docker-compose up neo4j

And do not forget to configure the connection settings in the application.properties.

spring.neo4j.uri=bolt://localhost:7687
spring.neo4j.authentication.username=neo4j
spring.neo4j.authentication.password=test

Now, you can run the application directly in IDEs, or using the following Maven command.

mvn spring-boot:run

After it run successfully, try to use curl command to verify the exposed APIs.

# curl http://localhost:8080/posts
[{"id":0,"title":"Post two","content":"The content of Post two","createdDate":"2020-11-04T10:35:14.1619567","updatedDate":"2020-11-04T10:35:14.1619567","createdBy":"hantsy","updatedBy":"hantsy"},{"id":1,"title":"Post one","content":"The content of Post one","createdDate":"2020-11-04T10:35:14.1481498","updatedDate":"2020-11-04T10:35:14.1481498","createdBy":"hantsy","updatedBy":"hantsy"}]

Grab the source code from my github.

Written by

Self-employed technical consultant, solution architect and full-stack developer

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store