Spring Boot MongoDB Reactive
last modified July 16, 2023
Spring Boot MongoDB Reactive tutorial shows how to do reactive programming with MongoDB in a Spring Boot application.
MongoDB
MongoDB is a NoSQL cross-platform document-oriented database. It is one of the most popular databases available. MongoDB is developed by MongoDB Inc. and is published as free and open-source software.
The Spring Data MongoDB project provides integration with the MongoDB document database.
Reactive programming
Reactive programming is a programming paradigm that is functional, event-based, non-blocking, asynchronous, and centered around data stream processing. The term reactive comes from the fact that we react to changes such as mouse clicks or I/O events.
Reactive applications scale better and are more efficient when we are dealing with lots of streaming data. Reactive applications are non-blocking; they're not using resources waiting for processes to finish.
When building a reactive application, we need it to be reactive all the way down to the database. We need to use a database that supports reactive programming. MongoDB is a database that has reactive support.
Reactive applications implement an event-based model where data is pushed to the consumer. The consumer of data is called a subscriber, because it subscribes to the publisher, which publishes asynchronous streams of data.
Spring Reactor
Spring Reactor is a reactive library for building non-blocking applications on the JVM based on the Reactive Streams Specification.
The Reactor Project offers two types of publishers: Mono
and
Flux
. Flux
is a publisher that produces 0 to N values.
Operations that return multiple elements use this type.
Mono
is a publisher that produces 0 to 1 value. It is used for
operations that return a single element.
Spring Boot MongoDB Reactive example
In the following application we use reactive programming with a MongoDB database.
test
database name.
pom.xml src ├───main │ ├───java │ │ └───com │ │ └───zetcode │ │ │ Application.java │ │ │ MyRunner.java │ │ ├───model │ │ │ City.java │ │ ├───repository │ │ │ CityRepository.java │ │ └───service │ │ CityService.java │ │ ICityService.java │ └───resources │ application.properties └───test └───java
This is the project structure of the Spring application.
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.zetcode</groupId> <artifactId>springbootmongodbreactive</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.7</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.6.7</version> </plugin> </plugins> </build> </project>
This is the Maven pom.xml
file. The spring-boot-starter-data-mongodb-reactive
is a Spring Boot starter for using MongoDB document-oriented database and Spring Data MongoDB Reactive.
spring.main.banner-mode=off
In the application.properties
, we turn off the Spring Boot banner
and set the logging properties. Spring Boot by default attempts to connect to
a locally hosted instance of MongoDB, using the test database.
# mongodb spring.data.mongodb.host=localhost spring.data.mongodb.port=27017 spring.data.mongodb.database=testdb
If we want to configure MongoDB, we can set the corresponding properties.
package com.zetcode.model; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; import java.util.Objects; @Document(value="cities") public class City { @Id private String id; private String name; private int population; public City() { } public City(String name, int population) { this.name = name; this.population = population; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getPopulation() { return population; } public void setPopulation(int population) { this.population = population; } @Override public int hashCode() { int hash = 7; hash = 79 * hash + Objects.hashCode(this.id); hash = 79 * hash + Objects.hashCode(this.name); hash = 79 * hash + this.population; return hash; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final City other = (City) obj; if (this.population != other.population) { return false; } if (!Objects.equals(this.name, other.name)) { return false; } return Objects.equals(this.id, other.id); } @Override public String toString() { var builder = new StringBuilder(); builder.append("City{id=").append(id).append(", name=") .append(name).append(", population=") .append(population).append("}"); return builder.toString(); } }
This is the City
bean which has three attributes: id
,
name
, and population
.
@Document(value="cities") public class City {
The bean is decorated with the optional @Document
annotation.
@Id private String id;
The id
is decorated with the @Id
annotation.
Spring automatically generates a new id for a newly generated city object.
package com.zetcode.repository; import com.zetcode.model.City; import org.springframework.context.annotation.Configuration; import org.springframework.data.mongodb.repository.ReactiveMongoRepository; @Configuration public interface CityRepository extends ReactiveMongoRepository<City, String> { }
By extending from the ReactiveMongoRepository
, we have a reactive
MongoDB repository.
package com.zetcode.service; import com.zetcode.model.City; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.util.List; public interface ICityService { Mono<City> insert(City city); Flux<City> saveAll(List<City> cities); Mono<City> findById(String id); Flux<City> findAll(); Mono<Void> deleteAll(); }
ICityService
contains five contract methods.
package com.zetcode; import com.zetcode.model.City; import com.zetcode.service.CityService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.util.List; @Component public class MyRunner implements CommandLineRunner { private static final Logger logger = LoggerFactory.getLogger(MyRunner.class); @Autowired private CityService cityService; @Override public void run(String... args) throws Exception { logger.info("Creating cities"); var cities = List.of(new City("Bratislava", 432000), new City("Budapest", 1759000), new City("Prague", 1280000), new City("Warsaw", 1748000)); Mono<Void> one = cityService.deleteAll(); Flux<City> two = cityService.saveAll(cities); Flux<City> three = cityService.findAll(); three.subscribe(city -> logger.info("{}", city)); Mono<Void> all = Mono.when(one, two, three); all.block(); } }
We have a command line runner. In its run
method
we access the MongoDB using reactive programming.
Mono<Void> one = cityService.deleteAll();
We delete all cities if there are any in the collection.
Flux<City> two = cityService.saveAll(cities);
We save a list of cities.
Flux<City> three = cityService.findAll(); three.subscribe(System.out::println);
We find all cities from the collection. We subscribe to the publisher
with the subscribe
method and print the retrieved cities
to the terminal.
Mono<Void> all = Mono.when(one, two, three);
With Mono.when
we aggregate the three publishers into a new Mono
that will be fulfilled when all sources have completed.
all.block();
With block
we trigger all three operations and wait for the completion.
Since we have a console application, we introduce a blocking operation in order to
get the results on the terminal.
The subscribe
method starts the work and returns immediately. We have
no guarantee that the operation is done when other parts of the application run.
The block
is a blocking operation: it triggers the operation and
waits for its completion.
package com.zetcode; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
This code sets up the Spring Boot application.
In this article we have learned how to program MongoDB using reactive programming model in a Spring Boot application.