一 连接数据库、获取 ReactiveMongoTemplate实例
通过spring 容器化方式实例化
@Configuration
public class AppConfig {
@Bean
public MongoClient mongoClient() {
return MongoClients.create("mongodb://xxxx:xxxx@localhost:27017/");
}
@Bean
public ReactiveMongoDatabaseFactory reactiveMongoDatabaseFactory(MongoClient mongoClient) {
ReactiveMongoDatabaseFactory mongo = new SimpleReactiveMongoDatabaseFactory(mongoClient,"mytest");
return mongo;
}
@Bean
public ReactiveMongoTemplate reactiveMongoTemplate(ReactiveMongoDatabaseFactory mongoClient) {
return new ReactiveMongoTemplate(mongoClient);
}
}
测试
@Autowired
private ReactiveMongoTemplate template;
@Test
public void test(){
Mono<Person> personMono = template.insert(new Person("张三", 40));
personMono.doOnNext(System.out::println)
.block();
}
二 ReactiveMongoTemplate常用API
2.1 基本使用
@Test
public void test3(){
template
.query(Person.class) // 指定映射域对象
.inCollection("person1") // 指定要查询集合
.as(PersonType.class) //结果类型映射
.matching(Query.query(Criteria.where("name").is("李四"))) //匹配文档
.all() //获取查询的所有文档 .first 获取查询的第一个文档 .one 检测结果集合是否只有一个或0个文档,返回文档
.doOnNext(System.out::println)
.blockLast();
}
2.2 索引操作API
@Test
public void test(){
// 创建索引
template
.indexOps(Person.class)
.ensureIndex(new Index().on("name", Sort.Direction.DESC).unique().sparse())
.block();
// 修改索引
template
.indexOps(Person.class)
.alterIndex("name_-1", IndexOptions.unique())
.block();
// 删除索引
template
.indexOps(Person.class)
.dropIndex("name_-1")
.block();
// 获取索引信息
template
.indexOps(Person.class)
.getIndexInfo()
.doOnNext(System.out::println)
.blockLast();
}
2.3 集合操作API
@Test
public void test() {
// 获取集合名称列表
template
.getCollectionNames()
.doOnNext(System.out::println)
.blockLast();
// 根据映射类型获取集合名称
System.out.println(template.getCollectionName(Person.class));
// 获取集合对像
template
.getMongoDatabase()
.map(db -> db.getCollection("person"))
.doOnNext(System.out::println)
.block();
// 获取集合对象
Document document = new Document();
document.put("name","王五");
document.put("id","1");
document.put("age",30);
template
.getCollection("person")
.doOnNext(documentMongoCollection -> documentMongoCollection.insertMany(List.of(document)))
.block();
// 创建集合
template
.createCollection("student")
.doOnNext(System.out::println)
.block();
// 删除集合
template
.dropCollection("student")
.block();
// 检查集合是否存在
template
.collectionExists("student")
.doOnNext(System.out::println)
.block();
}
2.4 数据插入API
@Test
public void test(){
// 保存数据 save需要遍历列表,一个个插入。
template
.save(new Person("1","王五",30))
.block();
//插入数据 insert可以一次性插入一个列表,而不用遍历,效率高
template
.insert(new Person("1","王五",30))
.block();
// 批量插入 insertAll和bulkOps的服务器性能相同。但是,bulkOps不会发布生命周期事件。
template
.insert(List.of(new Person("tom",10),new Person("jack",10)), Person.class)
.blockLast();
template
.insertAll(List.of(new Person("tom1",10),new Person("jack1",10)))
.blockLast();
template
.bulkOps(BulkOperations.BulkMode.ORDERED, Person.class)
.insert(List.of(new Person("tom2",10),new Person("jack2",10)))
.execute()
.block();
}
注意:
id属性 1 插入或保存时,如果未设置 id 属性值,则假定其值将由数据库自动生成。因此,要成功自动生成 ObjectId ,类中 Id 属性或字段的类型必须是 String 、 ObjectId 或 BigInteger 。 2 用 @Id 注释的属性或字段映射到 _id 字段或者提供名为 id 的属性保存目标集合 1 默认情况下是类名小写对应的集合 2 @Document("xxx") 类上的注解指定的集合 3 通过 MongoTemplate 的方法调用的最后一个参数来指定集合名称
2.5 数据修改API
@Test
public void test8(){
template
.update(Person.class)
.matching(Criteria.where("name").is("tom4"))
.apply(new Update().inc("age",100))
.all() // .first 只更新查到的第一个 .all 更新所有查到的数据 .upsert 有时更新,没时插入
.block();
// 更新匹配第一条数据
template
.updateFirst(Query.query(Criteria.where("age").lt(100)),new Update().inc("age",100), Person.class)
.block();
// 更新所有匹配数据 使用 $inc 修饰符更新
template
.updateMulti(Query.query(Criteria.where("age").lt(100)),new Update().inc("age",100), Person.class)
.block();
// 更新插入匹配数据
template
.upsert(Query.query(Criteria.where("age").lt(100)),new Update().inc("age",100), Person.class)
.block();
// 聚合管道更新
AggregationUpdate update = Aggregation.newUpdate()
.set("average").toValue(ArithmeticOperators.valueOf("age").avg());
template.update(Person.class)
.apply(update)
.all()
.block();
// 替换文档
template
.replace(Query.query(Criteria.where("name").is("jack2")),new Person("jack3333",30), ReplaceOptions.replaceOptions().upsert())
.block();
// 查找和修改 FindAndModifyOptions.options().returnNew(true) 返回修改后的值,不设置返回修改前的值
template
.findAndModify(Query.query(Criteria.where("name").is("jack222")),new Update().inc("age",1000), FindAndModifyOptions.options().returnNew(true),Person.class)
.doOnNext(System.out::println)
.block();
// 和上面等价
template
.update(Person.class)
.matching(Criteria.where("name").is("tom4"))
.apply(new Update().inc("age",100))
.withOptions(FindAndModifyOptions.options().returnNew(true))
.findAndModify() // .first 只更新查到的第一个 .all 更新所有查到的数据 .upsert 有时更新,没时插入
.block();
// findAndReplace()同理操作
}
2.6 数据删除API
@Test
public void test9(){
template.remove(Person.class);
template.remove(Query.query(Criteria.where("name").is("tom")), "person");
// 一次性删除
template.remove(new Query().limit(3), "person");
// 文档不会批量删除,而是逐个删除
template.findAllAndRemove(Query.query(Criteria.where("name").is("tom")), "person");
// 文档不会批量删除,而是逐个删除
template.findAllAndRemove(new Query().limit(3), "person");
}
2.7 数据查询API
1 基本查询
@Test
public void test(){
// QBC
template
.find(Query.query(Criteria.where("name").is("张三")), Person.class)
.doOnNext(System.out::println)
.blockLast();
template
.query(Person.class)
.matching(Query.query(Criteria.where("name").is("张三").and("age").is(130)))
.all()
.doOnNext(System.out::println)
.blockLast();
// 纯 JSON 字符串创建查询实例
template
.find(new BasicQuery("{name:'张三'}","{name:1,age:1}"), Person.class)
.doOnNext(System.out::println)
.blockLast();
}
2 Criteria 查询条件
@Test
public void test(){
// Criteria 查询条件
// 使用 $all 运算符创建条件,包含指定数组所有项
template
.query(Person.class)
.matching(Query.query(Criteria.where("cityId").all(3,4)))
.all()
.doOnNext(System.out::println)
.blockLast();
// and 运算
template
.query(Person.class)
.matching(Query.query(Criteria.where("cityId").all(3,4).and("name").is("张三1")))
.all()
.doOnNext(System.out::println)
.blockLast();
template
.query(Person.class)
.matching(Query.query(Criteria.where("cityId").all(3,4).andOperator(Criteria.where("name").is("张三1"))))
.all()
.doOnNext(System.out::println)
.blockLast();
}
其他查询条件见:
3 Query 查询方法
@Test
public void test12(){
// Query 查询方法
Query query = new Query();
query.addCriteria(Criteria.where("age").gt(30));
query.fields().include("name","age");
query.skip(2);
query.limit(5);
query.with(Sort.by("age").ascending());
query.withHint("{name:1}"); //指定索引查询
query.cursorBatchSize(100); //定义了每个响应批次中要返回的文档数
// 使用聚合表达式进行字段投影
// 使用本地表达式
query.fields().project(MongoExpression.create("'$toUpper' : '$name'")).as("name");
// 使用 AggregationExpression
query.fields().project(StringOperators.valueOf("name").toUpper()).as("name");
// 使用 SpEL 和 AggregationExpression 来调用表达式函数
query.fields().project(AggregationSpELExpression.expressionOf("toUpper(name)")).as("name");
template
.query(Person.class)
// .distinct("age") // 查询不重复值
.as(PersonType.class) // 结果投影
.matching(query)
.all()
.doOnNext(System.out::println)
.blockLast();
}
注意: @Field("xxx") 加在类属性上可以进行对应字段映射
三 ReactiveMongo Repository
3.1 如何定义相关存储库接口
1. 要定义特定于域类的存储库接口。接口必须扩展 Repository
并键入到域类和 ID 类型
@Document("user") // 指定域类到存储库映射,不指定是为类名小写
class User { … }
interface MyBaseRepository<T, ID> extends Repository<User, Long> {
Optional<T> findById(ID id);
<S extends T> S save(S entity);
}
2. 如果要公开该域类的 CRUD 方法,可以扩展 ReactiveCrudRepository 或其变体之一,而不是 Repository
。
interface MyBaseRepository<T, ID> extends ReactiveCrudRepository <User, Long> { … }
这样就提供了CRUD 功能的方法
3 如果应用程序中的许多存储库应具有相同的方法集,可以定义自己的基接口,然后从中继承
@NoRepositoryBean
interface MyBaseRepository<T, ID> extends Repository<T, ID> {
Optional<T> findById(ID id);
<S extends T> S save(S entity);
}
interface UserRepository extends MyBaseRepository<User, Long> {
User findByEmailAddress(EmailAddress emailAddress);
}
@NoRepositoryBean
interface MyBaseRepository<T, ID> extends CrudRepository<T, ID> { … }
interface AmbiguousUserRepository extends MyBaseRepository<User, Long> { … }
注意:这样的接口必须用 @NoRepositoryBean
进行注释。这可以防止 Spring Data 尝试直接创建它的实例而失败,因为它无法确定该存储库的实体。
3.2 基本使用
1 创建实体类
@Document("person")
public class Person {
@Id // 指定对应id字段
private String id;
private String name;
@Field("age") //指定映射字段
private int age1;
private List<Integer> cityId;
@Version
private Long version;
private Instant timestamp;
... set and get
}
2 创建仓库
public interface PersonRepository extends ReactiveMongoRepository<Person, ObjectId{}
3 测试
@Autowired
private PersonRepository repository;
@Test
public void test() {
repository
.findAll()
.doOnNext(System.out::println)
.blockLast();
}
3.3 定义查询方法
除了已经提供的查询方法,还可以从方法名称自定义创建查询
@Repository
public interface PersonRepository extends ReactiveMongoRepository<Person, ObjectId> {
Flux<Person> findByName(String name);
Flux<Person> findByNameAndAge1(String name,int age);
Flux<Person> findByNameIgnoreCase(String name);
Flux<Person> findByVersionOrderByAge1Desc(int age);
Flux<Person> findFirstByVersionOrderByAge1Desc(int age);
Flux<Person> findByNameOrAge1(String name,int age);
Flux<Person> findByNameLikeIgnoreCase(String name);
Flux<Person> findByAge1LessThan(int age);
Flux<Person> findByAge1(int age,Pageable pageable);
Flux<Person> findByAge1(int age,Sort sort);
Flux<Person> findByAge1(int age, Sort sort, Limit limit);
}
@Test
public void test() {
repository.findByName("张三1").doOnNext(System.out::println).blockLast();
// and
repository.findByNameAndAge1("张三1",30).doOnNext(System.out::println).blockLast();
// 忽略大小写
repository.findByNameIgnoreCase("Tom").doOnNext(System.out::println).blockLast();
// 降序排序
repository.findByVersionOrderByAge1Desc(4).doOnNext(System.out::println).blockLast();
// 查询第一个
repository.findFirstByVersionOrderByAge1Desc(4).doOnNext(System.out::println).blockLast();
// or
repository.findByNameOrAge1("张三",140).doOnNext(System.out::println).blockLast();
// like
repository.findByNameLikeIgnoreCase("T").doOnNext(System.out::println).blockLast();
// <
repository.findByAge1LessThan(140).doOnNext(System.out::println).blockLast();
// 分页 Pageable.unpaged() 不分页
repository.findByAge1(110, Pageable.ofSize(2).withPage(0)).doOnNext(System.out::println).blockLast();
repository.findByAge1(110, PageRequest.of(1,2,Sort.by("name").descending())).doOnNext(System.out::println).blockLast();
// 排序 Sort.unsorted() 不排序
repository.findByAge1(110, Sort.by("name").descending().and(Sort.by("version").ascending())).doOnNext(System.out::println).blockLast();
// 类型安全的 API 定义排序表达式
Sort.TypedSort<Person> personTypedSort = Sort.sort(Person.class);
repository.findByAge1(110, personTypedSort.by(Person::getAge1).descending().and(personTypedSort.by(Person::getName).ascending())).doOnNext(System.out::println).blockLast();
// 限制 Limit.unlimited() 不加限制
repository.findByAge1(110, Sort.by("name").descending(),Limit.of(3)).doOnNext(System.out::println).blockLast();
}
其他定义规则见: Repository query keywords
3.4 基于JSON的查询方法和字段限制
@Repository
public interface PersonRepository extends ReactiveMongoRepository<Person, ObjectId> {
@Query("{'name':?0}")
// 使用 ?0 占位符可以将方法参数中的值替换为 JSON 查询字符串
Flux<Person> findBySelfName(String name);
@Query(value = "{'name':?0}",fields = "{name:1,age:1}")
// fields 设置限制字段表达式
Flux<Person> findBySelfNameLimitFiled(String name);
@Query(value = "{'age':{$gt:?0}}",fields = "{name:1,age:1}")
Flux<Person> findByAge(int age);
@Aggregation("{ $group : { _id : $age, total : { $sum : $age } } }")
// 聚合查询
Flux<Object> findAndGroup();
}
3.5 按示例查询 QBE
1. 按示例查询 API 由四个部分组成:
- Probe:具有填充字段的域对象的实际实例。
- ExampleMatcher:如何匹配特定字段的详细信息。它可以在多个示例中重复使用。
- Example:
Example
由 Probe 和ExampleMatcher
组成。它用于创建查询。 - FetchableFluentQuery:提供流畅的 API,允许进一步自定义派生自
Example
的查询。使用 Fluent API 可以指定查询的顺序、投影和结果处理。
2 适用场景
- 使用一组静态或动态约束查询数据存储。
- 频繁重构域对象,无需担心破坏现有查询。
- 独立于底层数据存储 API 工作。
3 限制
- 不支持嵌套或分组属性约束,例如
firstname = ?0 or (firstname = ?1 and lastname = ?2)
。 - 特定于存储的字符串匹配支持。根据您的数据库,字符串匹配可以支持字符串的 starts/contains/ends/regex。
- 与其他属性类型完全匹配。
4 使用
@Test
public void test() {
Person personType = new Person();
personType.setName("Ja");
personType.setAge1(110);
ExampleMatcher exampleMatcher = ExampleMatcher
.matching()
.withIgnorePaths("age")
.withIncludeNullValues()
// .withStringMatcher(ExampleMatcher.StringMatcher.STARTING)
.withMatcher("name", matcher -> matcher.startsWith().ignoreCase());
Example<Person> personTypeExample = Example.of(personType,exampleMatcher);
repository.findAll(personTypeExample,Sort.by("age").descending()).doOnNext(System.out::println).blockLast();
// findBy 通过在第二个参数中 sortBy 允许您指定结果的顺序。 as 允许您指定希望将结果转换为的类型。 project 限制了查询的属性。 first 、 firstValue 、 one 、 oneValue 、 all 、 page 、 stream 、 count 和 exists 定义了获得的结果类型以及当可用结果数超过预期数量时查询的行为方式。
repository.findBy(personTypeExample ,q -> q.as(PersonType.class).sortBy(Sort.by("age").descending()).limit(3).all()).doOnNext(System.out::println).blockLast();
}
3.6 排序方式
public interface PersonRepository extends ReactiveMongoRepository<Person, String> {
Flux<Person> findByFirstnameSortByAgeDesc(String firstname);
Flux<Person> findByFirstname(String firstname, Sort sort);
@Query(sort = "{ age : -1 }")
Flux<Person> findByFirstname(String firstname);
@Query(sort = "{ age : -1 }")
Flux<Person> findByLastname(String lastname, Sort sort);
}
3.7 数据修改
@Repository
public interface PersonRepository extends ReactiveMongoRepository<Person, ObjectId> {
// 自定义函数名称查询 + @Update
@Update("{'$inc':{age:?1}}")
// 普通修改
Mono<Long> findAndIncrementAgeByName(String name,int age);
@Update("{'$inc':{age:?#{[1]}}}")
// Spel表达式修改
Mono<Long> findAndIncrementAgeSpelByName(String name,int age);
@Update(pipeline = "{ '$set' : { 'age' : { '$add' : [ '$age', ?1 ] } } }")
// 管道修改
Mono<Long> findAndIncrementAgePiplineByName(String name,int age);
@Update("{'$push':{cityId:?#{[1]}}}")
// Spel表达式修改
Mono<Long> findAndPushByName(String name,int age);
// @Query + @Update
@Query("{name:?0}")
@Update("{'$push':{cityId:?#{[1]}}}")
Mono<Long> findAndUpdateCityId(String name,int age);
}
3.8 数据删除
public interface PersonRepository extends ReactiveMongoRepository<Person, String> {
//返回类型 Flux 将检索并返回所有匹配的文档,然后再实际删除它们
Flux<Person> deleteByLastname(String lastname);
//直接删除匹配的文档,返回已删除的文档总数。
Mono<Long> deletePersonByLastname(String lastname);
//检索并删除第一个匹配的文档。
Mono<Person> deleteSingleByLastname(String lastname);
}
注意:根据返回类型确定具体操作
3.9 投影
1 基于接口投影
@Component
class MyBean {
String getFullStr(Person person){
return person.toString();
}
}
public interface NameAndAge {
// 闭合投影
String getName();
// 开放投影
@Value("#{target.age1}")
String getAge();
// SPEL表达式计算
@Value("#{target.name + '_' + target.age1}")
String getFull();
//获取方法参数
@Value("#{args[0] + '_' + target.name}")
String getPrefic(String prefix);
//复杂表达式计算
@Value("#{@myBean.getFullStr(target)}")
String getFullStr();
}
2 动态投影
interface PersonRepository extends Repository<Person, UUID> {
<T> Collection<T> ReactiveMongoRepository(String lastname, Class<T> type);
}
void someMethod(PersonRepository people) {
Flux<Person> aggregates =
people.findByLastname("Matthews", Person.class);
Flux<NamesOnly> aggregates =
people.findByLastname("Matthews", NamesOnly.class);
}
3.10 自定义仓库实现
interface HumanRepository {
void someHumanMethod(User user);
}
class HumanRepositoryImpl implements HumanRepository {
public void someHumanMethod(User user) {
// Your custom implementation
}
}
interface ContactRepository {
void someContactMethod(User user);
User anotherContactMethod(User user);
}
class ContactRepositoryImpl implements ContactRepository {
public void someContactMethod(User user) {
// Your custom implementation
}
public User anotherContactMethod(User user) {
// Your custom implementation
}
}
interface UserRepository extends CrudRepository<User, Long>, HumanRepository, ContactRepository {
// Declare query methods here
}
注意:实现类需要遵循命名约定,即附加默认为 Impl
的后缀。或者设置自定义后缀:
@EnableMongoRepositories(repositoryImplementationPostfix = "MyPostfix")
class Configuration { … }