Spring Data Mongo
Spring Data Mongo provides reactive variants of MongoTemplate
and MongoRepository
, aka ReactiveMongoTemplate
and ReactiveMongoRepository
which have reactive capabilities.
Getting Started
Follow the the Getting Started part to create a freestyle or Spring Boot based project skeleton.
For a freestyle Spring project, add the following into project dependencies.
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-reactivestreams</artifactId>
</dependency>
Create a @Configuration
class to enable Reactive support.
@EnableReactiveMongoRepositories(basePackageClasses = {MongoConfig.class})
public class MongoConfig extends AbstractReactiveMongoConfiguration {
@Value("${mongo.uri}")
String mongoUri;
@Override
public MongoClient mongoClient() {
return MongoClients.create(mongoUri);
}
@Override
protected String getDatabaseName() {
return "blog";
}
}
Create a new Post
MongoDB document class.
@Document
@Data
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
class Post {
@Id
private String id;
private String title;
private String content;
}
@Document
declares it as a MongoDB document.@Id
indicates it is the identifier field ofPost
document.
Declares a PostRepository
interface to extend Spring Data MongoDB specific ReactiveMongoRepository
.
interface PostRepository extends ReactiveMongoRepository<Post, String> {
}
Configure MongoDB connection in the appliation.yml file.
spring:
data:
mongodb:
uri: mongodb://localhost:27017/blog
Before starting up your application, make sure there is a running MongoDB instance in your local system.
NOTE: If you have not installed it, go to Mongo download page and get a copy of MongoDB, and install it into your system.
Alternatively, if you are familiar with Docker, it is simple to start a MongoDB instance via Docker Compose file.
version: '3.3' # specify docker-compose version
# Define the services/containers to be run
services:
redis:
image: redis
ports:
- "6379:6379"
mongodb:
image: mongo
volumes:
- mongodata:/data/db
ports:
- "27017:27017"
command: --smallfiles --rest
# command: --smallfiles --rest --auth
volumes:
mongodata:
Execute the following command to start a Mongo instance in a Docker container.
docker-compose up mongodb
When the Mongo service is started, it is ready for bootstrapping the application.
mvn spring-boot:run
For the complete codes, check spring-reactive-sample/data-mongo.
If you are using Spring Boot, the configuration can be simplified. Just need to add spring-boot-starter-data-mongodb-reactive
into the project dependencies.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>
No need extra configuration class, Spring Boot will enable reactive support for MongoDB in this project. ReactiveMongoTemplate
and ReactiveMongoRepository
will be configured automatically.
For the complete codes, check spring-reactive-sample/boot-data-mongo.
Add some sample data into MongoDB when starting the application.
Declare a CommandLineRunner
bean.
@Component
@Slf4j
class DataInitializr implements CommandLineRunner {
private final PostRepository posts;
public DataInitializr(PostRepository posts) {
this.posts = 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("content of " + title).build())
)
)
.log()
.subscribe(
null,
null,
() -> log.info("done initialization...")
);
}
}
Use a CommandLineRunner
to make sure the run
method is executed after the application is started.
Execute mvn spring-boot:run
to start up the application now, then we can test if the data is initialized successfully.
curl -v http://localhost:8080/posts
* timeout on name lookup is not supported
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /posts HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.1
> Accept: */*
>
< HTTP/1.1 200 OK
< transfer-encoding: chunked
< Content-Type: application/json;charset=UTF-8
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Content-Type-Options: nosniff
< X-Frame-Options: DENY
< X-XSS-Protection: 1 ; mode=block
<
[{"id":"599149d53c44062e08c58b86","title":"Post one","content":"content of Post one","createdDate":[2017,8,14,14,57,25,71000000]},{"id":"599149d53c44062e08c58b87","title":"Post two","content":"content of Post two","createdDate":[2017,8,14,14,57,25,173000000]}]* Connection #0 to host localhost left intact
Customizing Queries
As other Spring Data projects, Spring Data Mongo Reactive also query derivation in the Repository
.
For example:
interface PostRepository extends ReactiveMongoRepository<Post, String> {
Flux findByTitleContains(String title);
}
Or add q custom @Query
to execute the raw query statement on Mongo directly. The following is an example writing the query string in text block(since Java 13).
@Query(
value = """
{
"title" : {
"$regularExpression" : { "pattern" : ?0, "options" : ""}
}
}
""",
sort = """
{
"title" : 1 ,
"createdDate" : -1
}
"""
)
Flux<Post> findByKeyword(String q);
ReactiveMongoTemplate
The ReactiveMongoTemplate
provides a programmatic approach to execute queries in a fluent API.
The following is an example of the Repository
rewritten with ``ReactiveMongoTemplate` .
@Component
@RequiredArgsConstructor
class PostRepository {
private final ReactiveMongoTemplate template;
Flux<Post> findByKeyword(String q) {
var reg = ".*" + q + ".*";
return template
.find(query(where("title").regex(reg).orOperator(where("content").regex(reg))), Post.class);
}
Flux<Post> findByTitleContains(String title) {
var reg = ".*" + title + ".*";
return template
.find(query(where("title").regex(reg)), Post.class);
}
Flux<Post> findByTitleContains(String title, Pageable page) {
var reg = ".*" + title + ".*";
return template
.find(query(where("title").regex(reg)).with(page), Post.class);
}
public Flux<Post> findAll() {
return template.findAll(Post.class);
}
public Mono<Post> save(Post post) {
return template.save(post);
}
public Flux<Post> saveAll(List<Post> data) {
return Flux.fromIterable(data).flatMap(template::save);
}
public Mono<Post> findById(String id) {
return template.findById(id, Post.class);
}
public Mono<Long> deleteById(String id) {
//return template.remove(Post.class).matching(query(where("id").is(id))).all().map(DeleteResult::getDeletedCount)
return template.remove(query(where("id").is(id)), Post.class).map(DeleteResult::getDeletedCount);
}
public Mono<Long> deleteAll() {
return template.remove(Post.class).all().map(DeleteResult::getDeletedCount);
}
}
Pagination
Almost all reactive variants(Mongo, R2dbc etc.) are support Pageable
as a method parameter in the Repository
.
interface PostRepository extends ReactiveMongoRepository<Post, String> {
//...
Flux<PostSummary> findByTitleContains(String title, Pageable page);
}
Note, there is no findAll(Pageable page)
in the reactive repositories.
If you want to perform a pageable like operations on all items, use the following instead.
var all = posts.findAll()
.take(...)
.skip(...)
And all pageable query returns a Flux
result.
To get the count of items in the Mongo, perform another count
query.
For example:
Mono<Long> countByTitleContains(String title)
It can not return a
Page
object in Spring Data reactive API.
Data Auditing Support
Spring Data Mongo supports data auditing as Spring Data JPA, it can set the current user and created/last modified timestamp to a field automatically.
Add EnableMongoAuditing
to application class to activate auditing for MongoDB.
@EnableReactiveMongoAuditing
public class DemoApplication {}
In Post
document, add a new field createdDate
, annotated it with @CreatedDate
, it will fill the createdDate with current date when inserting it into MongoDB.
@CreatedDate
private LocalDateTime createdDate;
To fill the auditor automatically, create a ReactiveAuditorAware
bean.
@Bean
ReactiveAuditorAware<String> auditorAware() {
return () -> Mono.just("hantsy");
}
Add @CreatedBy
and @LastModifiedBy
set the current user that creating and modifying the entity.
class Post {
//...
@CreatedBy
private String createdBy;
@LastModifiedBy
private String updatedBy;
//...
}
For the complete codes, check spring-reactive-sample/boot-data-mongo.
QueryDSL
Spring Data Mongo provide reactive support in the its QueryDSL extension.
Add the following dependencies in your pom.xml.
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-mongodb</artifactId>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<scope>provided</scope>
</dependency>
And configure the apt-maven-plugin to generate QueryDSL metadata.
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/java</outputDirectory>
<!--<processor>com.querydsl.mongodb.morphia.MorphiaAnnotationProcessor</processor>-->
<processor>lombok.launch.AnnotationProcessorHider$AnnotationProcessor,org.springframework.data.mongodb.repository.support.MongoAnnotationProcessor
</processor>
</configuration>
</execution>
</executions>
</plugin>
Modify the PostRepository
as the following.
interface PostRepository extends ReactiveMongoRepository<Post, String>, ReactiveQuerydslPredicateExecutor<Post> {
}
An example to use QueryDSL API in your codes.
this.postRepository.findAll(QPost.post.title.containsIgnoreCase("my"))
For the complete codes, check spring-reactive-sample/boot-data-mongo-querydsl.
Tailable Query
The tailable
query is a Mongo specific feature. A tailable document works an infinite streams, performing a query on a tailable
document is similar to connect to a message broker, when a new document is inserted, it will be emitted to all query stream connected.
@Tailable
Flux<Message> readByAll()
The query stream can be subscribed by a SSE endpoint, a WebSocket endpoint or a RSocket message channel.
There is a simple example to expose data via SSE endpoint, check spring-reactive-sample/boot-data-mongo-tailable.
Please check the following more comprehensive examples, all provide Mongo tailable
documents as backend message stream.