目录
- 整合 Spring Data MongoDB 操作 MongoDB 数据库
整合 Spring Data MongoDB 操作 MongoDB 数据库
演示前提:
登录单机模式的 mongodb 服务器命令
mongod.exe --config "E:\install\mongodb\mongodb-4.2.25\mongod.conf"
登录【test】数据库的 mongodb 客户端命令
mongo mongodb://192.168.0.107:27017/test -u LJHAAA -p 123456
登录【admin】数据库的 mongodb 客户端命令
mongo mongodb://192.168.0.107:27017/admin -u admin -p 123456
Spring Data MongoDB
DAO 接口只需继承 CrudRepository 或 ReactiveCrudRepository ,Spring Data MongoDB 就能为 DAO 组件提供实现类。
Spring Data MongoDB 支持方法名关键字查询(全自动)。
Spring Data MongoDB支持用@Query定义查询语句(半自动),
只不过该 @Query 注解是 Spring Data MongoDB 提供的。
Spring Data MongoDB 同样支持 DAO 组件添加自定义的查询方法(手动)
——通过添加额外的父接口,并为额外的该接口提供实现类,Spring Data MongoDB 就能该实现类中的方法“移植”到DAO组件中。
Spring Data MongoDB 同样支持 Example 样本查询。
MongoTemplate 和 CrudRepository 的区别
MongoTemplate详细介绍
方法名关键字查询(全自动)
MongoDB 有自己的查询语法,因此 Spring Data MongoDB 支持的方法名关键字同样非常强大。
——这些方法名关键字都会转换成对应的查询操作符
关键字查询中涉及两个模糊查询的方法:Like 和 Regex,其中 Regex 执行正则表达式匹配的查询,这一点没有任何问题;
——但 Like 只是用普通的通配符,比如 * 可匹配任意个、任意字符。
@Query查询(半自动)
@Query("{name: {$regex: ?0}}")
Flux<Book> findByQuery2(String namePattern);
还有类似的如下注解:
@Aggregation: 该注解修饰的方法执行聚集操作。
MongoDB 集合提供 aggregate 方法,该方法需要一个 pipeline 的参数,该参数一个数组,每个数组元素是一次聚集运算。
@Aggregation: 同样需要 pipeline 属性,该属性完全等同于 pipeline 参数
@CountQuery: 该注解修饰的方法将只返回符合查询条件的文档的数量。
相当于将 count 属性设为 true 的 @Query
@DeleteQuery: 该注解修饰的方法将删除符合查询条件的文档。
相当于将 delete 属性设为 true 的 @Query
@ExistsQuery: 该注解修饰的方法将只返回是否包含符合查询条件的文档。
相当于将 exists 属性设为 true 的 @Query
@Query: 该注解修饰的方法将返回符合查询条件的文档。
自定义查询方法(全手动)
让 DAO 接口继承自定义 DAO 接口、并为自定义 DAO 接口提供实现类,可以为 DAO 组件添加自定义查询方法。Spring Data MongoDB 会将父接口的实现类中的查询方法移植到DAO组件中。
自定义查询方法既可用 MongoTemplate 执行查询,也可通过 MongoTemplate 的 execute() 方法调用 MongoDB 驱动的底层 API 执行查询。
【建议:】 Spring Data MongoDB 在定义数据类,建议使用 String 类型的 ID。
【问题:】 MongoDB 为什么没有支持自增长的主键?
MongoDB 和 MySQL、PgSQL 是不同,MongoDB 从开始就被设计成分布式环境的数据库,
——在分布式环境下使用自增长主键的性能相对是比较差的,因此 MongoDB 放弃了这种设计,改为使用 ObjectId 的主键。
方法名的开头关键字
方法名支持的关键字
Spring Data MongoDB的注解
Spring Data Mongo DB提供了如下注解:
@Document: 该注解指定将数据类映射到MongoDB的集合。
@MongoId: 映射标识属性。与@Id相比,该注解可指定标识属性的类型。
@Field: 该注解指定将数据类的属性映射到集合的字段。如果不使用该注解,默认基于同名映射原则。
Spring Data MongoDB 提供如下索引化注解:
@CompoundIndex和@CompoundIndexes: 用于指定对多个字段创建组合索引(也就是复合索引)。
其中@CompoundIndexes作为容器,为@CompoundIndex提供重复注解的支持。
@GeoSpatialIndexed: 指定对该属性使用MongoDB的地理空间索引功能创建索引。
@HashIndexed: 指定被注解修饰的属性应作为计算Hash索引的key。
@Indexed: 指定对普通类型的属性建立索引。
可指定 direction(1或1-1)、name、partialFilter、sparse、unique 正好对应于创建索引时可指定的选项。
@TextIndexed: 指定将字段作为全文检索索引的组成部分,MongoDB 的每个集合只能有一个全文检索索引,所有带 @TextIndexed 注解的字段都被合并到一个索引中。
代码演示
1、创建项目
勾选上同步和反应式的依赖
2、配置 MongoDB 连接
方式一:
Spring Boot 自动连接URL为 “mongodb://localhost/test” 的 MongoDB 服务器。
也可通过 spring.data.mongodb.uri 属性改变 MongoDB 服务器的 URL ,
例如通过如下属性指定 MongoDB 副本集的 URL :
spring.data.mongodb.uri=mongodb://user:pass@host1:12345,host2:23456/boot
上面配置指定了两个副本:位于host1:12345 的副本和位于 host2:23456的副本。
一旦指定了 spring.data.mongodb.uri,那就无需指定 host, port, credentials,replica属性。
简单来说,uri 相当于是指定连接 MongoDB 的快捷方式。
查看 MongoProperties 这个属性配置类
方式二:用这个
也可通过如下方式配置 MongoDB 服务器的地址:
# 连接mongodb数据库
spring.data.mongodb.host=localhost
spring.data.mongodb.port=27017
spring.data.mongodb.database=test
spring.data.mongodb.username=LJHAAA
spring.data.mongodb.password=123456
# 指定 spring.data.mongodb 根据实体类Book的字段上的索引注解(@Indexed、@TextIndexed )来创建索引
spring.data.mongodb.auto-index-creation=true
所有 spring.data.mongodb.* 开头的属性都由 MongoProperties 负责处理
3、添加 Book 实体类
4、添加 BookDao 接口
4-1: 方法名关键字查询的接口方法(全自动)
继承 ReactiveCrudRepository 接口,属于反应式编程,属于异步操作。
反应式编程是一种面向异步数据流的编程范式,其核心思想是通过观察者模式和函数式编程的方式处理数据流。
4-2: @Query查询的接口方法(半自动)
4-3: 自定义查询方法(全手动)
更新中~
5、基本的增删改查 – 测试类演示
5-1:往 books 集合添加一条文档,自己指定id值
代码
结果
成功往mongodb数据库里面的【books】集合添加了一条文档数据
Tree View 视图
Table View 视图
5-2:往 books 集合添加多条文档,由mongodb生成id值
代码
结果
5-3:根据 id 查询文档
代码
结果
5-4:根据 id 查询文档并修改该文档
代码
看每个方法调用的返回值
结果
5-5:根据 id 删除文档
代码
结果
id 为 1 的文档已经被删除了
6、方法名关键字查询(全自动查询)-- 测试类演示
6-1:根据名字查询文档
代码
结果
6-2:根据价格范围查询
代码
结果
6-3:根据【author】字段进行【通配符】查询
代码
结果
6-4:通过名字来进行正则表达式查询
代码
结果
6-5:查询价格大于指定参数值的文档有几条
代码
结果
7、@Query查询(半自动查询)-- 测试类演示
7-1:通过关键字对文档进行全文检索
代码
结果
7-2:通过 【作者】 和 【价格大于指定参数值】 来查询文档
代码
结果
8、自定义查询(全手动)-- 测试类演示
1、写一个 Dao 接口,自定义查询方法
在接口写上自己自定义的查询方法,然后作为父接口,让 BookDao 接口去继承。
2、自定义查询方法的实现类及测试类演示
2-1:要求书名【name】匹配这个这个正则表达式【nameRegex】,且价格【price】大于这个【startPrice】
自定义查询方法的实现类
测试代码
进行单元测试
测试结果
2-2:查询价格在这个范围的文档
自定义查询方法的实现类
测试代码
测试结果
9、样本查询 – 测试类演示
1、样本查询接口
这里的 DAO 接口已经继承了这个可以实现样本查询的接口了。
2、测试方法
3、测试结果
完整代码
Book 实体类
package cn.ljh.mongoboot.domain;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.index.TextIndexed;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.FieldType;
import org.springframework.data.mongodb.core.mapping.MongoId;
/**
* author JH 2024-03
*/
//映射到mongodb数据库里面的【books】集合
@Document("books")
@Data
public class Book
{
// id 的类型定义成String,灵活性比较大
// @MongoId(FieldType.INT64)
//普通的 @Id 注解更合适
@Id
private String id;
//给【name】字段建立索引
@Indexed
private String name;
@Indexed
private double price;
private String author;
//表示 【desc】 字段映射到数据库集合中的【description】字段列
@Field("description")
@TextIndexed //目前全文检索默认不支持中文
private String desc;
//无参构造器
public Book(){}
//有参构造器
public Book( String name, double price, String author, String desc)
{
this.name = name;
this.price = price;
this.author = author;
this.desc = desc;
}
}
application.properties 配置类
# 连接mongodb数据库
spring.data.mongodb.host=localhost
spring.data.mongodb.port=27017
spring.data.mongodb.database=test
spring.data.mongodb.username=LJHAAA
spring.data.mongodb.password=123456
# 指定 spring.data.mongodb 根据实体类Book的字段上的索引注解(@Indexed、@TextIndexed )来创建索引
spring.data.mongodb.auto-index-creation=true
BookDao 方法名关键字查询和@Query查询的接口
package cn.ljh.mongoboot.dao;
import cn.ljh.mongoboot.domain.Book;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.data.repository.query.ReactiveQueryByExampleExecutor;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* author JH 2024-03
*/
//类型参数1:操作的实体类 ; 类型参数2:实体类的主键类型
public interface BookDao extends
ReactiveCrudRepository<Book, String>,
ReactiveQueryByExampleExecutor<Book>,
CustomBookDao
{
//===================================================方法名关键字查询(全自动)==========================================
//返回值为 Flux ,表示接收多个返回值 ; 返回值为 Mono ,表示接收单个返回值
//根据【关键字】,查询【name】字段包含该关键字的文档
Flux<Book> findByName(String name);
//根据【关键字】,对【price】字段的值进行范围查询
Flux<Book> findByPriceBetween(double startPrice, double endPrice);
//根据【关键字】,通过【通配符】形式查询【author】字段包含该关键字的文档
Flux<Book> findByAuthorLike(String authorPattern);
//根据【关键字】,通过【正则表达式】方式查询【name】字段包含该关键字的文档
Flux<Book> findByNameRegex(String name);
//查询【price】字段的值大于指定参数值(关键字)的文档【有几条】
Mono<Integer> countByPriceGreaterThan(double startPrice);
//===================================================@Query查询(半自动)==============================================
//通过关键字 term 对文档进行全文检索
@Query("{$text: {$search: ?0}}")
Flux<Book> findByText(String term);
//通过 【author】字段 和 【price】价格大于指定参数值 来查询文档
@Query("{author: ?0 ,price:{$gt: ?1}}")
Flux<Book> findByQuery(String author, double startPrice);
}
CustomBookDao 自定义查询方法的接口
package cn.ljh.mongoboot.dao;
import cn.ljh.mongoboot.domain.Book;
import reactor.core.publisher.Flux;
/**
* author JH 2024-03
*/
//自定义查询方法的接口
public interface CustomBookDao
{
//要求书名【name】匹配这个这个正则表达式【nameRegex】,且价格【price】大于这个【startPrice】
Flux<Book> findByCustomRegexAndPrice(String nameRegex , double startPrice);
//查询价格在这个范围的文档
Flux<Book> findByCustomPrice(double startPrice , double endPrice);
}
CustomBookDaoImpl 自定义查询方法的实现类
package cn.ljh.mongoboot.dao.impl;
import cn.ljh.mongoboot.dao.CustomBookDao;
import cn.ljh.mongoboot.domain.Book;
import com.mongodb.BasicDBObject;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import reactor.core.publisher.Flux;
/**
* author JH 2024-03
*/
public class CustomBookDaoImpl implements CustomBookDao
{
@Autowired
private ReactiveMongoTemplate mongoTemplate;
@Override
public Flux<Book> findByCustomRegexAndPrice(String nameRegex, double startPrice)
{
//Spring Data MongoDB 提供了一个 Criteria类 来构建 query 这个查询对象
// where是Criteria类的一个静态方法,用于指定要查询的字段或属性
// Criteria.where("name")表示对"name"字段进行查询操作,
// .regex(nameRegex)表示使用正则表达式进行匹配,nameRegex是传入的名称的正则表达式
// .and("price")表示在上述查询条件的基础上再加入"price"字段的查询条件
// .gt(startPrice)表示查询大于给定起始价格的值
Query query = Query.query(
Criteria
.where("name").regex(nameRegex) //查询条件1
.and("price").gt(startPrice)); //查询条件2
//query:代表查询条件 ; Book.class:要查询的实体对象
Flux<Book> bookFlux = mongoTemplate.find(query, Book.class);
return bookFlux;
}
@Override
public Flux<Book> findByCustomPrice(double startPrice, double endPrice)
{
//.execute 方法用来执行一个MongoDB查询操作
//mongoCollection,来自 MongoDB的驱动 API ,代表一个 collection
Flux<Book> bookFlux = mongoTemplate.execute(Book.class, mongoCollection ->
{
//自定义的查询条件是这样的:{price: { $gt: startPrice , $lt:endPrice }}
//这个BasicDBObject 就是代表查询条件中的一个对象
BasicDBObject cond = new BasicDBObject();
//给这个对象设置查询条件,就能得到这个 { $gt: startPrice , $lt:endPrice }
cond.put("$gt", startPrice);
cond.put("$lt", endPrice);
//再创建一个对象
BasicDBObject bson = new BasicDBObject();
//再把查询条件设置进去,就得到这个 {price: { $gt: startPrice , $lt:endPrice }} 查询对象
bson.put("price", cond);
return Flux.from(mongoCollection.find(bson))
.map(document ->
{
Book book = new Book(
(String) document.get("name"),
(Double) document.get("price"),
(String) document.get("author"),
(String) document.get("description"));
//因为 id 有普通的string类型,也有 objectId 类型,所以需要做判断
Object id = document.get("_id");
//如果 id 是 ObjectId 类型; instanceof是Java中的一个运算符,用于检查一个对象是否属于某个特定的类型或其子类型
if (id instanceof ObjectId)
{
ObjectId obId = (ObjectId) id;
//toHexString是ObjectId类的一个方法,用于将ObjectId对象转换为十六进制字符串表示形式
book.setId(obId.toHexString());
} else
{ book.setId((String) id); }
return book;
});
});
return bookFlux;
}
}
BookDaoTest 测试类
package cn.ljh.mongoboot.dao;
import cn.ljh.mongoboot.domain.Book;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.mongodb.repository.Query;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Optional;
/**
* author JH 2024-03
*/
//表示不要用web环境来进行测试
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class BookDaoTest
{
//依赖注入
@Autowired
private BookDao bookDao;
//==============================================增删改查==============================================================
//添加一个文档到books集合里面,该文档的id为自己指定的
@ParameterizedTest //表示这个方法是一个参数化的测试
//需要多个参数进行测试,用这个注解
@CsvSource({
//因为 mongodb 目前不支持中文进行全文检索,所以把内容写成英文来测试
"1,火影忍者,100,岸本齐史,this cartoon is very good"
})
public void testSaveWithId(String id, String name, double price, String author, String desc)
{
Book b = new Book(name, price, author, desc);
//自己设置id
b.setId(id);
Mono<Book> saveResult = bookDao.save(b);
//使用同步方式测试反应式API
//saveResult.block()是一个同步方法。在调用block()方法时,它会阻塞当前线程,直到saveResult的操作完成并返回结果
Book book = saveResult.block();
System.err.println(book);
}
//添加多个文档到books集合里面,该文档的id为mongodb自己指定的
//表示这个方法是一个参数化的测试
@ParameterizedTest
//需要多个参数进行测试,用这个注解
@CsvSource({
"家庭教师,200,天野明,aa this cartoon is jiatingjiaoshi",
"七龙珠,300,鸟山明,aa this cartoon is very qilongzhu",
"蜡笔小新,400,臼井仪人,bb this cartoon is very labixiaoxin"
})
public void testSaveWithId(String name, double price, String author, String desc)
{
Book b = new Book(name, price, author, desc);
Mono<Book> saveResult = bookDao.save(b);
//使用同步方式测试反应式API
//saveResult.block()是一个同步方法。在调用block()方法时,它会阻塞当前线程,直到saveResult的操作完成并返回结果
Book book = saveResult.block();
System.err.println(book);
}
//根据id查询文档
@ParameterizedTest
//测试方法只需要一个参数用这个注解
@ValueSource(strings = {
"1",
"65eda80aec60bd4deae6f38b"
})
public void testFindById(String id)
{
Book block = bookDao.findById(id).block();
System.err.println(block);
}
//对文档进行修改
@Test
public void testUpdate()
{
//bookDao.findById("1") 返回的是一个Mono<Book>类型的对象
Mono<Book> bookMono = bookDao.findById("1");
//blockOptional()是Mono类型的一个方法,它会阻塞当前线程并返回一个Optional对象。如果findById操作成功,Optional对象将包含查找到的图书对象;否则,Optional对象将为空
Optional<Book> b = bookMono.blockOptional();
//如果Optional对象中存在图书对象,则执行ifPresent中的逻辑
b.ifPresent(
//使用Lambda表达式的方式对查找到的图书进行操作
book ->
{
//进行修改操作
book.setName(book.getName() + "AAAAA");
//使用.block()方法阻塞当前线程,直到保存操作完成。这样确保更新操作在调用block()之前完成,并且等待操作结果返回
bookDao.save(book).block();
}
);
}
//根据id查询文档
@ParameterizedTest
//测试方法只需要一个参数用这个注解
@ValueSource(strings = {
"1",
})
public void testDeleteById(String id)
{
//因为反应式是异步操作,有可能出现删除操作还没完成,这个测试方法已经先一步执行完成了,所以导致删除失败
//所以用.block()方法阻塞当前线程,直到删除操作完成,再继续执行这个测试方法
bookDao.deleteById(id).block();
}
//==============================================方法名关键字查询(全自动查询)============================================
//根据名字查询文档
//表示这个方法是一个参数化的测试方法
@ParameterizedTest
//只需要一个参数用这个注解
@ValueSource(strings = {
"火影忍者",
"七龙珠"
})
public void testFindByName(String name)
{
//反应式--异步操作
Flux<Book> book = bookDao.findByName(name);
//将Flux<Book>类型的对象转换为Iterable<Book>类型的对象,这样可以遍历处理异步操作的结果
//Iterable是Java中的一个接口,用于表示可迭代的对象集合。
Iterable<Book> books = book.toIterable();
books.forEach(System.err::println);
}
//根据价格范围查询
@ParameterizedTest
@CsvSource({
"50,250",
"150,450"
})
public void testFindByPriceBetween(double startPrice, double endPrice)
{
Flux<Book> bookFlux = bookDao.findByPriceBetween(startPrice, endPrice);
bookFlux.toIterable().forEach(System.err::println);
}
//根据【author】字段进行【通配符】查询
@ParameterizedTest
@ValueSource(strings = {
"天*",
"岸*"
})
public void testFindByAuthorLike(String authorPattern)
{
Flux<Book> bookFlux = bookDao.findByAuthorLike(authorPattern);
bookFlux.toIterable().forEach(System.err::println);
}
//通过名字来进行正则表达式查询
@ParameterizedTest
@ValueSource(strings = {
// ^ 符号表示开头,表示必须由【火】字开头; 这个 . 这个点表示匹配任意字符; $ 符号表示结尾
"^火.+$",
//^ . 表示任意符号开头,中间包含【小】,后面的.表示任意符号结尾
"^.+小.+$"
})
public void testFindByNameRegex(String name)
{
Flux<Book> bookFlux = bookDao.findByNameRegex(name);
bookFlux.toIterable().forEach(System.err::println);
}
//查询价格大于指定参数值的文档有几条
@ParameterizedTest
@ValueSource(doubles = {
100.0,
200.0
})
public void testCountByPriceGreaterThan(double startPrice)
{
System.err.println("price 大于【 " + startPrice + " 】的文档有【 " + bookDao.countByPriceGreaterThan(startPrice).block() + " 】条");
}
//===================================================@Query查询(半自动)==============================================
//通过关键字 term 对文档进行全文检索
@ParameterizedTest
@ValueSource(strings = {
"good",
"aa"
})
public void testFindByText(String term)
{
Flux<Book> bookFlux = bookDao.findByText(term);
bookFlux.toIterable().forEach(System.err::println);
}
//通过 作者 和 价格大于指定参数值 来查询文档
@ParameterizedTest
@CsvSource({
"天野明,50",
"天野明,500"
})
public void testFindByQuery(String author, double startPrice)
{
Flux<Book> bookFlux = bookDao.findByQuery(author, startPrice);
bookFlux.toIterable().forEach(System.err::println);
}
//==============================================自定义查询(全手动查询)=================================================
//要求书名【name】匹配这个这个正则表达式【nameRegex】,且价格【price】大于这个【startPrice】
@ParameterizedTest
@CsvSource({
//^ 表示开头 , 点 . 表示任意字符 ,$ 表示结尾 :全部就是以任意字符开头,然后中间有个【影】字,然后以任意字符结尾
"^.+影.+$ , 50",
"^.+教.+$, 50"
})
public void testFindByCustomRegexAndPrice(String nameRegex, double startPrice)
{
Flux<Book> bookFlux = bookDao.findByCustomRegexAndPrice(nameRegex, startPrice);
bookFlux.toIterable().forEach(System.err::println);
}
//查询价格在这个范围的文档
@ParameterizedTest
@CsvSource({
"99,199",
"199,399"
})
public void testFindByCustomPrice(double startPrice, double endPrice)
{
Flux<Book> bookFlux = bookDao.findByCustomPrice(startPrice, endPrice);
bookFlux.toIterable().forEach(System.err::println);
}
//==============================================样本查询=============================================================
@ParameterizedTest
@CsvSource({
"火影忍者,岸本齐史",
"家庭教师,天野明明明"
})
public void testByExanple(String name, String author)
{
//构建一个样本查询的对象
Example<Book> example = Example.of(
new Book(name, 0.0, author, null),
ExampleMatcher.matching()
.withIgnoreNullValues() //忽略 null 属性
.withIgnorePaths("price") //忽略 price 属性
);
Flux<Book> bookFlux = bookDao.findAll(example);
Iterable<Book> books = bookFlux.toIterable();
books.forEach(System.err::println);
}
}
pom.xml 依赖文档
<?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 https://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>2.5.3</version>
</parent>
<groupId>cn.ljh</groupId>
<artifactId>mongoboot</artifactId>
<version>1.0.0</version>
<name>mongoboot</name>
<properties>
<java.version>11</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 同步的 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<!-- 反应式 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>