Spring Data MongoDB with Reactive MongoDB

Spring Data MongoDB with Reactive MongoDB

2 Comments

Last Updated on June 14, 2019 by Simanta

Spring Data MongoDB has been updated to leverage the reactive programming model introduced in Spring Framework 5. This was followed by support for reactive data access for NoSQL databases, such as MongoDB, Cassandra, and Redis.

With the rise in popularity of NoSQL databases, MongoDB has rapidly gained popularity in the enterprise and the Spring community.

I have published both a post and a video for setting up MongoDB within a Spring Boot application.

In this post, we’ll take a look at using the reactive programming features in Spring Framework 5 and Spring Data MongoDB.

If you’re new to Reactive programming, I’ll suggest you first go through What are Reactive Streams in Java? post, followed by the Spring Web Reactive post.

The Maven POM

For this post, I’m using Embedded MongoDB. I want the benefit of talking to an instance loaded in memory with the same capabilities as my production environment. This makes development and testing blazing fast.

You can check my post to configure and use Embedded MongoDB in a Spring Boot application here.

The dependency to bring in the Embedded MongoDB is this.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<scope>runtime</scope>
</dependency>
<dependency> <groupId>de.flapdoodle.embed</groupId> <artifactId>de.flapdoodle.embed.mongo</artifactId> <scope>runtime</scope> </dependency>
<dependency>
   <groupId>de.flapdoodle.embed</groupId>
   <artifactId>de.flapdoodle.embed.mongo</artifactId>
   <scope>runtime</scope>
</dependency>

The whole capability of Reactive MongoDB lies on the MongoDB driver. The official MongoDB Reactive Streams Java Driver implements the Reactive Streams API for interoperability with other reactive stream implementations. The reactive driver provides asynchronous stream processing with non-blocking back pressure for MongoDB.

To use the driver, add this dependency.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-reactivestreams</artifactId>
<version>1.5.0</version>
</dependency>
<dependency> <groupId>org.mongodb</groupId> <artifactId>mongodb-driver-reactivestreams</artifactId> <version>1.5.0</version> </dependency>
<dependency>
   <groupId>org.mongodb</groupId>
   <artifactId>mongodb-driver-reactivestreams</artifactId>
   <version>1.5.0</version>
</dependency>

Here is the complete

pom.xml
pom.xml.

pom.xml

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
</parent>
<artifactId>spring-boot-reactive-mongodb</artifactId>
<name>SpringBoot Reactive MongoDB</name>
<properties>
<spring-data-releasetrain.version>Kay-M1</spring-data-releasetrain.version>
<spring.version>5.0.0.M3</spring.version>
<reactor.version>3.0.3.RELEASE</reactor.version>
<mongodb-driver-reactivestreams.version>1.5.0</mongodb-driver-reactivestreams.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-reactivestreams</artifactId>
<version>${mongodb-driver-reactivestreams.version}</version>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<repositories>
<repository>
<id>spring-libs-snapshot</id>
<url>https://repo.spring.io/libs-snapshot</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-libs-snapshot</id>
<url>https://repo.spring.io/libs-snapshot</url>
</pluginRepository>
</pluginRepositories>
</project>
<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> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.4.RELEASE</version> </parent> <artifactId>spring-boot-reactive-mongodb</artifactId> <name>SpringBoot Reactive MongoDB</name> <properties> <spring-data-releasetrain.version>Kay-M1</spring-data-releasetrain.version> <spring.version>5.0.0.M3</spring.version> <reactor.version>3.0.3.RELEASE</reactor.version> <mongodb-driver-reactivestreams.version>1.5.0</mongodb-driver-reactivestreams.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-mongodb</artifactId> </dependency> <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-core</artifactId> </dependency> <dependency> <groupId>org.mongodb</groupId> <artifactId>mongodb-driver-reactivestreams</artifactId> <version>${mongodb-driver-reactivestreams.version}</version> </dependency> <dependency> <groupId>de.flapdoodle.embed</groupId> <artifactId>de.flapdoodle.embed.mongo</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <repositories> <repository> <id>spring-libs-snapshot</id> <url>https://repo.spring.io/libs-snapshot</url> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>spring-libs-snapshot</id> <url>https://repo.spring.io/libs-snapshot</url> </pluginRepository> </pluginRepositories> </project>
<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>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.4.RELEASE</version>
    </parent>
    <artifactId>spring-boot-reactive-mongodb</artifactId>
    <name>SpringBoot Reactive MongoDB</name>
    <properties>
        <spring-data-releasetrain.version>Kay-M1</spring-data-releasetrain.version>
        <spring.version>5.0.0.M3</spring.version>
        <reactor.version>3.0.3.RELEASE</reactor.version>
        <mongodb-driver-reactivestreams.version>1.5.0</mongodb-driver-reactivestreams.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-mongodb</artifactId>
        </dependency>
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mongodb</groupId>
            <artifactId>mongodb-driver-reactivestreams</artifactId>
            <version>${mongodb-driver-reactivestreams.version}</version>
        </dependency>
        <dependency>
            <groupId>de.flapdoodle.embed</groupId>
            <artifactId>de.flapdoodle.embed.mongo</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <repositories>
        <repository>
            <id>spring-libs-snapshot</id>
            <url>https://repo.spring.io/libs-snapshot</url>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-libs-snapshot</id>
            <url>https://repo.spring.io/libs-snapshot</url>
        </pluginRepository>
    </pluginRepositories>
</project>

 

The Domain Object

I have written a

Product
Product domain object for this post. Products have a name, description, price, and product URL.

Product.java

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
package guru.springframework.domain;
import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import java.math.BigDecimal;
@Document
public class Product {
@Id
private ObjectId _id;
private String name;
private String description;
private BigDecimal price;
private String imageUrl;
public Product(String name, String description, BigDecimal price, String imageUrl) {
this.name = name;
this.description = description;
this.price = price;
this.imageUrl = imageUrl;
}
public ObjectId getId() {
return _id;
}
public void setId(ObjectId id) {
this._id = id;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public String getImageUrl() {
return imageUrl;
}
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
}
package guru.springframework.domain; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; import java.math.BigDecimal; @Document public class Product { @Id private ObjectId _id; private String name; private String description; private BigDecimal price; private String imageUrl; public Product(String name, String description, BigDecimal price, String imageUrl) { this.name = name; this.description = description; this.price = price; this.imageUrl = imageUrl; } public ObjectId getId() { return _id; } public void setId(ObjectId id) { this._id = id; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public BigDecimal getPrice() { return price; } public void setPrice(BigDecimal price) { this.price = price; } public String getImageUrl() { return imageUrl; } public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; } }
package guru.springframework.domain;

import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

import java.math.BigDecimal;

@Document
public class Product {

    @Id
    private ObjectId _id;
    private String name;
    private String description;
    private BigDecimal price;
    private String imageUrl;

    public Product(String name, String description, BigDecimal price, String imageUrl) {
        this.name = name;
        this.description = description;
        this.price = price;
        this.imageUrl = imageUrl;
    }

    public ObjectId getId() {
        return _id;
    }

    public void setId(ObjectId id) {
        this._id = id;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public BigDecimal getPrice() {
        return price;
    }

    public void setPrice(BigDecimal price) {
        this.price = price;
    }

    public String getImageUrl() {
        return imageUrl;
    }

    public void setImageUrl(String imageUrl) {
        this.imageUrl = imageUrl;
    }
}

 

Spring Data MongoDB Reactive CRUD Repository

If you have worked with Spring Data in a Spring Boot application, you are familiar with the repository pattern.  You extend

CrudRepository
CrudRepository or its sub interface, and Spring Data MongoDB will generate the implementation for you.

Reactive repositories work the same way. You extend your repository interface from

ReactiveCrudRepository
ReactiveCrudRepository, specify domain-specific query methods, and rely on Spring Data MongoDB to provide the implementations.

ReactiveCrudRepository
ReactiveCrudRepository uses reactive types introduced in Spring Framework 5. These are 
Mono
Mono and
Flux
Flux which implement Reactive Streams.

Here is reactive repository interface.

ReactiveProductRepository.java

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
package guru.springframework.repositories;
import guru.springframework.domain.Product;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
public interface ReactiveProductRepository extends ReactiveCrudRepository<Product, String> {
Flux<Product> findByName(String name);
Flux<Product> findByName(Mono<String> name);
Mono<Product> findByNameAndImageUrl(Mono<String> name, String imageUrl);
@Query("{ 'name': ?0, 'imageUrl': ?1}")
Mono<Product> findByNameAndImageUrl(String name, String imageUrl);
}
package guru.springframework.repositories; import guru.springframework.domain.Product; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.data.mongodb.repository.Query; import org.springframework.data.repository.reactive.ReactiveCrudRepository; public interface ReactiveProductRepository extends ReactiveCrudRepository<Product, String> { Flux<Product> findByName(String name); Flux<Product> findByName(Mono<String> name); Mono<Product> findByNameAndImageUrl(Mono<String> name, String imageUrl); @Query("{ 'name': ?0, 'imageUrl': ?1}") Mono<Product> findByNameAndImageUrl(String name, String imageUrl); }
package guru.springframework.repositories;

import guru.springframework.domain.Product;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;

public interface ReactiveProductRepository extends ReactiveCrudRepository<Product, String> {
    Flux<Product> findByName(String name);

    Flux<Product> findByName(Mono<String> name);

    Mono<Product> findByNameAndImageUrl(Mono<String> name, String imageUrl);

    @Query("{ 'name': ?0, 'imageUrl': ?1}")
    Mono<Product> findByNameAndImageUrl(String name, String imageUrl);
}

 

As you can see, in this

ReactiveProductRepository
ReactiveProductRepository interface, the repository uses reactive types as return types.

Reactive repositories in Spring Data MongoDB can also use reactive types for parameters. The overloaded

findByName()
findByName() and
findByNameAndImageUrl()
findByNameAndImageUrl() methods are examples of this.

Configuration for Spring Data MongoDB Reactive Repositories

The configuration class is similar to a non-reactive one. Along with some infrastructural setup, we have the

@EnableReactiveMongoRepositories
@EnableReactiveMongoRepositories annotation that activates support for reactive Spring Data.

The code of the

ApplicationConfiguration
ApplicationConfiguration class is this.

ApplicationConfiguration.java

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
package guru.springframework;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.env.Environment;
import org.springframework.data.mongodb.config.AbstractReactiveMongoConfiguration;
import org.springframework.data.mongodb.core.mapping.event.LoggingEventListener;
import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories;
import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoClients;
@SpringBootApplication(exclude = {MongoAutoConfiguration.class, MongoDataAutoConfiguration.class})
@EnableReactiveMongoRepositories
@AutoConfigureAfter(EmbeddedMongoAutoConfiguration.class)
class ApplicationConfiguration extends AbstractReactiveMongoConfiguration {
private final Environment environment;
public ApplicationConfiguration(Environment environment) {
this.environment = environment;
}
@Override
@Bean
@DependsOn("embeddedMongoServer")
public MongoClient mongoClient() {
int port = environment.getProperty("local.mongo.port", Integer.class);
return MongoClients.create(String.format("mongodb://localhost:%d", port));
}
@Override
protected String getDatabaseName() {
return "reactive-mongo";
}
}
package guru.springframework; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration; import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; import org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.DependsOn; import org.springframework.core.env.Environment; import org.springframework.data.mongodb.config.AbstractReactiveMongoConfiguration; import org.springframework.data.mongodb.core.mapping.event.LoggingEventListener; import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories; import com.mongodb.reactivestreams.client.MongoClient; import com.mongodb.reactivestreams.client.MongoClients; @SpringBootApplication(exclude = {MongoAutoConfiguration.class, MongoDataAutoConfiguration.class}) @EnableReactiveMongoRepositories @AutoConfigureAfter(EmbeddedMongoAutoConfiguration.class) class ApplicationConfiguration extends AbstractReactiveMongoConfiguration { private final Environment environment; public ApplicationConfiguration(Environment environment) { this.environment = environment; } @Override @Bean @DependsOn("embeddedMongoServer") public MongoClient mongoClient() { int port = environment.getProperty("local.mongo.port", Integer.class); return MongoClients.create(String.format("mongodb://localhost:%d", port)); } @Override protected String getDatabaseName() { return "reactive-mongo"; } }
package guru.springframework;


import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.env.Environment;
import org.springframework.data.mongodb.config.AbstractReactiveMongoConfiguration;
import org.springframework.data.mongodb.core.mapping.event.LoggingEventListener;
import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories;

import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoClients;

@SpringBootApplication(exclude = {MongoAutoConfiguration.class, MongoDataAutoConfiguration.class})
@EnableReactiveMongoRepositories
@AutoConfigureAfter(EmbeddedMongoAutoConfiguration.class)
class ApplicationConfiguration extends AbstractReactiveMongoConfiguration {
    private final Environment environment;

    public ApplicationConfiguration(Environment environment) {
        this.environment = environment;
    }

    @Override
    @Bean
    @DependsOn("embeddedMongoServer")
    public MongoClient mongoClient() {
        int port = environment.getProperty("local.mongo.port", Integer.class);
        return MongoClients.create(String.format("mongodb://localhost:%d", port));
    }

    @Override
    protected String getDatabaseName() {
        return "reactive-mongo";
    }
}

 

This

ApplicationConfiguration
ApplicationConfiguration class extends
AbstractReactiveMongoConfiguration
AbstractReactiveMongoConfiguration, the base class for reactive Spring Data MongoDB configuration. The
mongoClient()
mongoClient() method is annotated with
@Bean
@Bean to explicitly declare a configurable
MongoClient
MongoClient bean that represents a pool of connections for MongoDB.

Spring Data MongoDB Integration Tests

Let’s write few integration tests for the repository layer to verify that our code is using reactive MongoDB as expected.

Here is the integration test code:

ReactiveProductRepositoryIntegrationTest.java

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
package guru.springframework;
import static org.assertj.core.api.Assertions.*;
import guru.springframework.domain.Product;
import guru.springframework.repositories.ReactiveProductRepository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.math.BigDecimal;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.mongodb.core.CollectionOptions;
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class ReactiveProductRepositoryIntegrationTest {
@Autowired
ReactiveProductRepository repository;
@Autowired
ReactiveMongoOperations operations;
@Before
public void setUp() {
operations.collectionExists(Product.class)
.flatMap(exists -> exists ? operations.dropCollection(Product.class) : Mono.just(exists))
.flatMap(o -> operations.createCollection(Product.class, new CollectionOptions(1024 * 1024, 100, true)))
.then()
.block();
repository
.save(Flux.just(new Product("T Shirt", "Spring Guru printed T Shirt", new BigDecimal(125), "tshirt1.png"),
new Product("T Shirt", "Spring Guru plain T Shirt", new BigDecimal(115), "tshirt2.png"),
new Product("Mug", "Spring Guru printed Mug", new BigDecimal(39), "mug1.png"),
new Product("Cap", "Spring Guru printed Cap", new BigDecimal(66), "cap1.png")))
.then()
.block();
}
@Test
public void findByNameAndImageUrlWithStringQueryTest() {
Product mug = repository.findByNameAndImageUrl("Mug", "mug1.png")
.block();
assertThat(mug).isNotNull();
}
@Test
public void findByNameAndImageUrlWithMonoQueryTest() {
Product cap = repository.findByNameAndImageUrl(Mono.just("Cap"), "cap1.png")
.block();
assertThat(cap).isNotNull();
}
@Test
public void findByNameWithStringQueryTest() {
List<Product> tShirts = repository.findByName("T Shirt")
.collectList()
.block();
assertThat(tShirts).hasSize(2);
}
@Test
public void findByNameWithMonoQueryTest() {
List<Product> tShirts = repository.findByName(Mono.just("T Shirt"))
.collectList()
.block();
assertThat(tShirts).hasSize(2);
}
}
package guru.springframework; import static org.assertj.core.api.Assertions.*; import guru.springframework.domain.Product; import guru.springframework.repositories.ReactiveProductRepository; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.math.BigDecimal; import java.util.List; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.mongodb.core.CollectionOptions; import org.springframework.data.mongodb.core.ReactiveMongoOperations; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class ReactiveProductRepositoryIntegrationTest { @Autowired ReactiveProductRepository repository; @Autowired ReactiveMongoOperations operations; @Before public void setUp() { operations.collectionExists(Product.class) .flatMap(exists -> exists ? operations.dropCollection(Product.class) : Mono.just(exists)) .flatMap(o -> operations.createCollection(Product.class, new CollectionOptions(1024 * 1024, 100, true))) .then() .block(); repository .save(Flux.just(new Product("T Shirt", "Spring Guru printed T Shirt", new BigDecimal(125), "tshirt1.png"), new Product("T Shirt", "Spring Guru plain T Shirt", new BigDecimal(115), "tshirt2.png"), new Product("Mug", "Spring Guru printed Mug", new BigDecimal(39), "mug1.png"), new Product("Cap", "Spring Guru printed Cap", new BigDecimal(66), "cap1.png"))) .then() .block(); } @Test public void findByNameAndImageUrlWithStringQueryTest() { Product mug = repository.findByNameAndImageUrl("Mug", "mug1.png") .block(); assertThat(mug).isNotNull(); } @Test public void findByNameAndImageUrlWithMonoQueryTest() { Product cap = repository.findByNameAndImageUrl(Mono.just("Cap"), "cap1.png") .block(); assertThat(cap).isNotNull(); } @Test public void findByNameWithStringQueryTest() { List<Product> tShirts = repository.findByName("T Shirt") .collectList() .block(); assertThat(tShirts).hasSize(2); } @Test public void findByNameWithMonoQueryTest() { List<Product> tShirts = repository.findByName(Mono.just("T Shirt")) .collectList() .block(); assertThat(tShirts).hasSize(2); } }
package guru.springframework;

import static org.assertj.core.api.Assertions.*;

import guru.springframework.domain.Product;
import guru.springframework.repositories.ReactiveProductRepository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.math.BigDecimal;
import java.util.List;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.mongodb.core.CollectionOptions;
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class ReactiveProductRepositoryIntegrationTest {

    @Autowired
    ReactiveProductRepository repository;
    @Autowired
    ReactiveMongoOperations operations;

    @Before
    public void setUp() {
        operations.collectionExists(Product.class)
                .flatMap(exists -> exists ? operations.dropCollection(Product.class) : Mono.just(exists))
                .flatMap(o -> operations.createCollection(Product.class, new CollectionOptions(1024 * 1024, 100, true)))
                .then()
                .block();

        repository
                .save(Flux.just(new Product("T Shirt", "Spring Guru printed T Shirt", new BigDecimal(125), "tshirt1.png"),
                        new Product("T Shirt", "Spring Guru plain T Shirt", new BigDecimal(115), "tshirt2.png"),
                        new Product("Mug", "Spring Guru printed Mug", new BigDecimal(39), "mug1.png"),
                        new Product("Cap", "Spring Guru printed Cap", new BigDecimal(66), "cap1.png")))

                .then()
                .block();
    }

    @Test
    public void findByNameAndImageUrlWithStringQueryTest() {

        Product mug = repository.findByNameAndImageUrl("Mug", "mug1.png")
                .block();
        assertThat(mug).isNotNull();
    }

    @Test
    public void findByNameAndImageUrlWithMonoQueryTest() {
        Product cap = repository.findByNameAndImageUrl(Mono.just("Cap"), "cap1.png")
                .block();
        assertThat(cap).isNotNull();
    }

    @Test
    public void findByNameWithStringQueryTest() {
        List<Product> tShirts = repository.findByName("T Shirt")
                .collectList()
                .block();
        assertThat(tShirts).hasSize(2);
    }

    @Test
    public void findByNameWithMonoQueryTest() {
        List<Product> tShirts = repository.findByName(Mono.just("T Shirt"))
                .collectList()
                .block();
        assertThat(tShirts).hasSize(2);
    }


}

 

In the test class, we autowired in two Spring Beans.

Our

ReactiveProductRepository
ReactiveProductRepository implementation that Spring Data MongoDB provides and a
ReactiveMongoOperations
ReactiveMongoOperations implementation.

ReactiveMongoOperations
ReactiveMongoOperations is the interface for the main reactive Template API class,
ReactiveMongoTemplate
ReactiveMongoTemplate. This interface defines a basic set of reactive data access operations using Project Reactor
Mono
Mono and
Flux
Flux reactive types.

ReactiveMongoOperations
ReactiveMongoOperations contains reactive counterpart for most of the operations available in the
MongoOperations
MongoOperations interface of the traditional blocking template API.

The setup portion of our integration test will drop any existing documents and re-create the Product collection. The setup method then inserts 4 new documents into our MongoDB collection.

We’re calling the

.block()
.block()  method to ensure processing completes before the next command is executed.

Here is the output of the Integration tests from IntelliJ:

Test Output from Spring Data MongoDB reactive tests

You can get the complete source code for this post here.

About jt

    You May Also Like

    2 comments on “Spring Data MongoDB with Reactive MongoDB

    1. January 29, 2018 at 3:36 am

      Nice article. I was looking for reactive postgresql library for my application.

      Reply
    2. May 8, 2018 at 9:15 am

      Thank you for sharing this article, I really enjoyed reading it.

      Just a question: have you ever tried to compare your solution with a classical non-reactive in order to understand the scalability of the last? Well, I’m struggling to make solution based on reactive Mongo repository performant as non-reactive solution but I keep on seeing better result with non-blocking.

      I published my result on github https://github.com/MarcoGhise/ReactiveMongoPerformance and I’d really appreciate to know what you think about this issue.

      Thank you!

      Reply

    Leave a Reply

    Your email address will not be published. Required fields are marked *

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