Spring Boot - 数据库集成05 - 集成MongoDB

Spring Boot集成MongoDB

文章目录

  • Spring Boot集成MongoDB
    • 一:使用前的准备
      • 1:依赖导入 & 配置
      • 2:实体类创建
    • 二:核心 - MongoRepository
    • 三:核心 - MongoTemplate
      • 1:集合操作
      • 2:文档操作(重点)
      • 3:索引操作
    • 四:基础项目演示
      • 1:repository介绍
      • 2:template介绍
      • 3:ExampleMatcher介绍

使用Spring Data 框架都是按照面向对象思想操作用于的工具。

使用Spring Data Mongodb 也是使用面向对象的方式进行操作MongoDB,省略了使用Mongodb的Java客户端API把Document转换为实体类的过程

在这里插入图片描述

一:使用前的准备

1:依赖导入 & 配置

<!-- spring-boot-data-mongodb -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
spring:
  data:
    mongodb:
      host: localhost # mongodb的服务器ip
      port: 27017 # 服务端口号,默认是27017
      # username: cui # 用户名
      # password: 123456 # 密码
      database: test # 当前项目连接的数据库;
      # url: mongodb://cui:123456@192.168.229.137:27017/test # url是前五项的合集写法
      
      # authentication-database: test # 认证的数据库(连接用的账号,不在连接的库下时使用);
      # auto-index-creation: on # 是否自动创建索引的配置;

      # ---------- 副本集写法,必须是写在url中
      # connect:连接模式,指定为replicaSet代表连接副本集群;
      # slaveOk:从节点是否可读,为true表示可读,执行语句时,读操作自动发往从节点;
      # replicaSet:副本集群的名称,这里为cui;
      # --> mongodb://cui:123456 <- 用户名:密码
      # uri: mongodb://cui:123456@192.168.229.137:27018,192.168.229.137:27019,192.168.229.137:27020/test?connect=replicaSet&slaveOk=true&replicaSet=cui

      # ---------- 分片集群配置,必须写在url中,不需要跟option参数
      # ---------- 只需要配置所有mongo所在的IP、端口即可
      # uri: mongodb://cui:123456@192.168.229.137:27024,192.168.229.137:27025/test

2:实体类创建

package com.cui.springmongodemo.entity;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

import java.io.Serializable;

/**
 * <p>
 * 功能描述:实体类
 * ---------- 各个注解作用范围和含义如下
 * /@Document:作用于类上面,被该注解修饰的类,会和MongoDB中的集合相映射,如果类名和集合名不一致,可以通过collection参数来指定。
 * /@Id:标识一个字段为主键,可以加在任意字段上,但如果该字段不为_id,每次插入需要自己生成全局唯一的主键;如果不设置@Id主键,MongoDB会默认插入一个_id值来作为主键。
 * /@Transient:被该注解修饰的属性,在CRUD操作发生时,SpringData会自动将其忽略,不会被传递给MongoDB。
 * /@Field:作用于普通属性上,如果Java属性名和MongoDB字段名不一致,可以通过该注解来做别名映射。
 * /@DBRef:一般用来修饰“嵌套文档”字段,主要用于关联另一个文档。
 * /@Indexed:可作用于任意属性上,被该注解修饰的属性,如果MongoDB中还未创建索引,在第一次插入时,SpringData会默认为其创建一个普通索引。
 * /@CompoundIndex:作用于类上,表示创建复合索引,可以通过name参数指定索引名,def参数指定组成索引的字段及排序方式。
 * /@GeoSpatialIndexed、@TextIndexed:和上面的@Indexed注解作用相同,前者代表空间索引,后者代表全文索引。
 * </p>
 *
 * @author cui haida
 * @date 2023/11/25/16:43
 */
@Data
@Document(collection = "animals") // @Document -> 作用于类上面,被该注解修饰的类,会和MongoDB中的集合相映射
public class Animals implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id // 标识一个字段为主键,可以加在任意字段上,但如果该字段不为_id,每次插入需要自己生成全局唯一的主键
    private Integer id;

    private String name;

    private Integer age;

    private String color;

    private Food food;
}
package com.cui.springmongodemo.entity;

import lombok.Data;

import java.io.Serializable;

/**
 * <p>
 * 功能描述:演示对象
 * </p>
 *
 * @author cui haida
 * @date 2023/11/25/16:45
 */
@Data
public class Food implements Serializable {

    private static final long serialVersionUID = 1L;

    private String name;
    private String grade;
}

二:核心 - MongoRepository

基本操作都在这里,只要你的命名足够规范

自定义方法名开头 + 方法名开头跟的关键字 + 字段名 + 字段名称后面可以接的关键字

在这里插入图片描述

package com.cui.springmongodemo.repository;

import com.cui.springmongodemo.entity.Animals;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.repository.DeleteQuery;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * 通过标准名写法自动生成语句
 *
 * @author cui haida
 * @date 2023/11/25/16:47
 */
@Repository
public interface AnimalsRepository extends MongoRepository<Animals, Integer> {
    // 查询指定年龄的动物的数量
    Integer countByAge(Integer age);

    // 对动物的名称进行全模糊查询
    List<Animals> findByNameLike(String keyword);

    // 查询时同时满足年龄和姓名两个条件
    Animals findByAgeAndName(int age, String name);

    // 查询满足颜色、年龄其中任一条件的所有动物
    List<Animals> findByColorOrAge(String color, int age);

    // 查询第一个带有颜色的动物
    Animals findFirstByColorNotNull();

    // 查询年龄大于等于指定岁数的动物
    List<Animals> findByAgeGreaterThanEqual(int age);

    // 对id进行多值查询
    List<Animals> findByIdIn(List<Integer> ids);

    // 查询指定颜色的动物,并按照年龄降序返回
    List<Animals> findByColorOrderByAgeDesc(String color);

    // 查询年龄小于指定岁数的前三条数据
    List<Animals> findTop3ByAgeLessThan(int age);

    // 分页查询
    Page<Animals> findByAgeNotNull(Pageable pageable);

    // 注解式写法,自定义
    // @Query这用于自定义查询语句,其中声明根据name字段进行查询
    // ?0表示方法参数的索引(占位符),此处的0表示第一个参数name
    
    // 除此之外,还有另外几个注解,分别对应其他操作:
    // @Update:用于自定义更新语句的注解;
    // @DeleteQuery:用于自定义删除语句的注解;
    // @CountQuery:用于自定义统计语句的注解;
    // @ExistsQuery:用于自定义查询语句,但执行后只返回是否存在满足条件的数据,并不返回具体的文档;
    // @Aggregation:用于自定义聚合管道语句的注解;
    @Query("{'age': {$lt: ?0}}") // age要比传入的参数小才满足条件
    List<Animals> queryXxx(int age);
}
package com.cui.springmongodemo.service;

import com.cui.springmongodemo.entity.Animals;
import org.springframework.data.domain.Page;

import java.util.List;

/**
 * <p>
 * 功能描述:
 * </p>
 *
 * @author cui haida
 * @date 2023/11/25/16:49
 */
public interface AnimalsService {
    /**
     * 新增保存动物
     * @param animals 动物对象
     */
    void save(Animals animals);

    /**
     * 通过id删除
     * @param id 要删除的对象的id
     */
    void deleteById(Integer id);

    /**
     * 更新动物
     * @param animals 要更新的动物
     */
    void update(Animals animals);

    /**
     * 通过id返回指定的动物
     * @param id 要查询的动物的id
     * @return 查询到的动物对象
     */
    Animals findById(Integer id);

    /**
     * 拿到集合中的所有的动物对象
     * @return 所有的动物对象
     */
    List<Animals> findAll();

    /**
     * 查询年龄小于指定岁数的前三条数据
     * @param age 年龄
     * @return 年龄小于指定岁数的前三条数据
     */
    List<Animals> findTop3(int age);

    /**
     * 分页测试
     * @param pageNumber 页码
     * @param pageSize 页大小
     * @return 分页结果
     */
    Page<Animals> findByAgeNotNull(int pageNumber, int pageSize);

    /**
     * 自定义注解测试
     * @param age 年龄
     * @return 返回 < 输入年龄的动物集合
     */
    List<Animals> queryXxx(int age);

    /**
     * 事务测试
     */
    void mongoTransaction();
}
package com.cui.springmongodemo.service.impl;

import com.alibaba.fastjson2.JSONObject;
import com.cui.springmongodemo.entity.Animals;
import com.cui.springmongodemo.repository.AnimalsRepository;
import com.cui.springmongodemo.service.AnimalsService;
import com.mongodb.client.ClientSession;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import lombok.extern.slf4j.Slf4j;
import org.bson.Document;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;
import java.util.Optional;

/**
 * <p>
 * 功能描述:逻辑层实现类
 * </p>
 *
 * @author cui haida
 * @date 2023/11/25/16:53
 */
@Service
@Slf4j
public class AnimalsServiceImpl implements AnimalsService {

    @Resource
    private AnimalsRepository animalsRepository;

    @Resource
    private MongoClient mongoClient;

    @Override
    public void save(Animals animals) {
        animalsRepository.save(animals);
    }

    @Override
    public void deleteById(Integer id) {
        animalsRepository.deleteById(id);
    }

    @Override
    public void update(Animals animals) {
        animalsRepository.save(animals);
    }

    @Override
    public Animals findById(Integer id) {
        Optional<Animals> animals = animalsRepository.findById(id);
        if (animals.isPresent()) {
            return animals.get();
        } else {
            log.info("没有找到对应的实体");
            return null;
        }
    }

    @Override
    public List<Animals> findAll() {
        return animalsRepository.findAll();
    }

    @Override
    public List<Animals> findTop3(int age) {
        return animalsRepository.findTop3ByAgeLessThan(age);
    }

    @Override
    public Page<Animals> findByAgeNotNull(int pageNumber, int pageSize) {
        PageRequest pageRequest = PageRequest.of(pageNumber, pageSize);
        return animalsRepository.findByAgeNotNull(pageRequest);
    }

    @Override
    public List<Animals> queryXxx(int age) {
        return animalsRepository.queryXxx(age);
    }

    // 其实很少用mongo的事务机制
    @Override
    public void mongoTransaction() {
        // 1.先通过mongoClient开启一个session会话
        ClientSession session = mongoClient.startSession();
        try{
            // 2.通过session开启事务
            session.startTransaction();

            // 3.创建一个实体对象
            Animals animals = new Animals();
            animals.setId(222);
            animals.setName("白白");
            animals.setColor("白色");
            animals.setAge(1);

            // 4.通过mongoClient获取集合对象
            MongoCollection<Document> collection = mongoClient.getDatabase("test").getCollection("animals");

            // 5.通过集合对象提供的insert方法插入数据
            collection.insertOne(
                    session,
                    Document.parse(JSONObject.toJSONString(animals))
            );

            // 6.模拟执行异常
            int n = 100 / 0;

            // 7.如果执行到这里,说明执行没报错,提交事务
            session.commitTransaction();
        } catch (Exception e) {
            // 8.如果进入了catch,说明出现异常,回滚事务
            session.abortTransaction();
            e.printStackTrace();
        }
        // 9.关闭前面开启的session会话
        session.close();
    }
}

三:核心 - MongoTemplate

1:集合操作

一:创建集合

// 要创建集合的名称,collectionOptions -> 参数
mongoTemplate.createCollection(collectionName, [collectionOptions]);

public Integer createCollectionFixedSize(String collectionName, Long size, Long maxDocCount) {
    CollectionOptions collectionOptions = CollectionOptions.empty()
        .capped() // 创建固定集合。固定集合是指有着固定大小的集合,当达到最大值时,它会自动覆盖最早的文档
        .size(size) // 固定集合指定一个最大值,以千字节计(KB),如果 capped 为 true,也需要指定该字段
        .maxDocuments(maxDocCount); // 指定固定集合中包含文档的最大数量
    mongoTemplate.createCollection(collectionName, collectionOptions);
    return mongoTemplate.collectionExists(collectionName) ? 1 : 0;
}

二:查询集合

// 查询所有的集合的名称
mongoTemplate.getCollectionNames();

// 指定集合是否存在
mongoTemplate.collectionExists(collectionName);

三:删除集合

// 删除指定的集合
mongoTemplate.getCollection(collectionName).drop();

2:文档操作(重点)

一:插入文档(insert)

insert方法返回值是新增的Document对象,里面包含了新增后_id的值。

如果集合不存在会自动创建集合。

通过Spring Data MongoDB还会给集合中多加一个_class的属性,存储新增时Document对应Java中类的全限定路径。

这么做为了查询时能把Document转换为Java中的类型。

// 单个插入
User newUser = mongoTemplate.insert(user, COLLECTION_NAME);
// 插入多个
Collection<User> newUserList = mongoTemplate.insert(userList, COLLECTION_NAME);

二:存储文档(save)

在Mongodb中无论是使用客户端API还是使用Spring Data,更新返回结果一定是受影响行数。

if -> 更新后的结果和更新前的结果是相同,返回0。

如果使用save方法,要求所有的属性的值都给到了,否则只能使用update方法

User newUser = mongoTemplate.save(user, COLLECTION_NAME);

三:查询文档(criteria + query + find)

// findAll
mongoTemplate.findAll(User.class, COLLECTION_NAME);
// find by id
mongoTemplate.findById(id, User.class, COLLECTION_NAME);
// 其他常规的Criteria
Criteria sex = Criteria.where("sex").is(user.getSex()); // 准则1
Criteria age = Criteria.where("age").is(user.getAge()); // 准则2
// Criteria criteria = Criteria.where("age").gt(minAge).lte(maxAge); 可能有其他类型的条件
Criteria criteria = new Criteria().andOperator(age, sex); // 准则拼接,and
query = new Query(criteria); // 准则封装到条件中
mongoTemplate.find(query, User.class, COLLECTION_NAME);

四:更新文档(upsert & save & update)

🎉 save也可以看做是一种更新

// 创建条件对象【指明要更新谁】
Criteria criteria = Criteria.where("age").is(30);
// 创建查询对象,然后将条件对象添加到其中
Query query = new Query(criteria);
// 创建更新对象,并设置更新的内容
Update update = new Update().set("age", 33).set("name", "zhangsansan");
// upsert没有找到匹配查询的文档,则创建并插入一个新文档
UpdateResult result = mongoTemplate.upsert(query, update, User.class, COLLECTION_NAME);
// mongoTemplate.updateFirst
// mongoTemplate.updateMulti

五:删除文档(remove)

// 创建条件对象
Criteria criteria = Criteria.where("age").is(age).and("sex").is(sex);
// 创建查询对象,然后将条件对象添加到其中
Query query = new Query(criteria);
// 执行删除查找到的匹配的全部文档信息
DeleteResult result = mongoTemplate.remove(query, COLLECTION_NAME);
// mongoTemplate.findAndRemove(query, User.class, COLLECTION_NAME);
// mongoTemplate.findAllAndRemove(query, User.class, COLLECTION_NAME);

六:分页操作(query with + PageRequest)

PageRequest是Pageable接口的实现类。里面有protected的构造方法和名称为of的静态方法。

  • PageRequest.of(page,size)
  • PageRequest.of(page,size,Sort) 先排序后分页
  • PageRequest.of(page,size,Direction,properties) 排序后分页
Criteria criteria = Criteria.where("age").is(age);
Query query = new Query(criteria);
int pageIndex = 0;
int pageSize = 2;
query.with(PageRequest.of(pageIndex, pageSize, Sort.Direction.DESC, "age")); // 先通过age倒序排序后分页
List<User> list = mongoTemplate.find(query, User.class);

七:聚合操作(Aggregation newAggregation + stage aggregation )

比较复杂,但是核心就是组装每一个stage,然后封装到newAggregation中

Aggregation aggregation = Aggregation.newAggregation(
    // stage1:分组 -> 基于年龄字段分组,接着统计每组数量,并为统计字段取别名
    Aggregation.group("age").count().as("count"),
    // stage2:基于分组后的_id字段(原age)字段排序(升序)
    Aggregation.sort(Sort.Direction.ASC, "_id")
);
return mongoTemplate.aggregate(aggregation, "animals", Map.class);
package com.cui.springmongodemo.service;

import com.cui.springmongodemo.entity.Animals;
import com.mongodb.client.result.UpdateResult;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;

import java.util.List;
import java.util.Map;

/**
 * <p>
 * 功能描述:
 * </p>
 *
 * @author cui haida
 * @date 2023/11/25/18:34
 */
public interface MongoTemplateService {

    /**
     * 聚合操作测试
     */
    AggregationResults<Map> aggOp();

    /**
     * 聚合操作测试
     */
    AggregationResults<Map> aggOpOfMaxAndMin();

    /* 案例二:
     * 1:过滤掉food为空,以及age小于3岁的数据
     * 2:接着按food.grade分组,
     * 3:并求出每组的平均年龄、以及输出每组第一个、最后一个、所有动物姓名,
     * 4:最后按照平均年龄升序排列
     * */
    AggregationResults<Map> aggOp2();

    /* 案例三:
    * 1:先过滤掉food为空的数据
    * 2:基于food.grade分组
    * 3:且保留原文档,并输出分组字段值、原文档的name、age字段、原文档在各分组中的下标
    * 4:同时要支持分页功能
    * */
    List<AggregationResults<Map>> aggOp3();
}
package com.cui.springmongodemo.service.impl;

import com.cui.springmongodemo.entity.Animals;
import com.cui.springmongodemo.service.MongoTemplateService;
import com.mongodb.client.result.UpdateResult;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * <p>
 * 功能描述:
 * </p>
 *
 * @author cui haida
 * @date 2023/11/25/18:35
 */
@Service
public class MongoTemplateServiceImpl implements MongoTemplateService {

    @Resource
    private MongoTemplate mongoTemplate;

    @Override
    public AggregationResults<Map> aggOp() {
        Aggregation aggregation = Aggregation.newAggregation(
            // 基于年龄字段分组,接着统计每组数量,并为统计字段取别名
            Aggregation.group("age").count().as("count"),
            // 基于分组后的_id字段(原age)字段排序(升序)
            Aggregation.sort(Sort.Direction.ASC, "_id")
        );
        return mongoTemplate.aggregate(aggregation, "animals", Map.class);
    }

    @Override
    public AggregationResults<Map> aggOpOfMaxAndMin() {
        Aggregation aggregation = Aggregation.newAggregation(
            Aggregation.group("color")
            .max("age").as("max_age")
            .min("age").as("min_age")
            .avg("age").as("avg_age"),
            Aggregation.sort(Sort.Direction.DESC, "max_age")
        );

        return mongoTemplate.aggregate(aggregation, "animals", Map.class);
    }

    /* 案例二:
     * 1:过滤掉food为空,以及age小于3岁的数据
     * 2:接着按food.grade分组,
     * 3:并求出每组的平均年龄、以及输出每组第一个、最后一个、所有动物姓名,
     * 4:最后按照平均年龄升序排列
     * */
    @Override
    public AggregationResults<Map> aggOp2() {
        // 过滤掉food为空,以及age小于3岁的数据的条件
        Criteria criteria = Criteria.where("food").exists(true).and("age").gte(3);
        
        Aggregation aggregation = Aggregation.newAggregation(
            // 1:match先进行过滤
            Aggregation.match(criteria),
            // 2:分组 + 聚合函数
            Aggregation.group("food.grade") // 通过food.grade进行分组,然后统计每一个分组的信息
            .avg("age").as("avg_age")
            .first("name").as("first_name")
            .last("name").as("last_name")
            .push("name").as("names"),
            // 排序
            Aggregation.sort(Sort.Direction.ASC, "age")
        );
        return mongoTemplate.aggregate(aggregation, "animals", Map.class);
    }

    /* 案例三:
     * 1:先过滤掉food为空的数据
     * 2:基于food.grade分组
     * 3:且保留原文档,并输出分组字段值、原文档的name、age字段、原文档在各分组中的下标
     * 4:同时要支持分页功能
     * */
    @Override
    public List<AggregationResults<Map>> aggOp3() {
        // 1.每页的数据量为2条
        int pageSize = 2;
        // 2.过滤掉food字段为空的条件对象
        Criteria criteria = Criteria.where("food").exists(true);

        List<AggregationResults<Map>> ans = new ArrayList<>();

        // 3.用for循环模拟三个分页请求
        for (int pageNumber = 1; pageNumber <= 3; pageNumber++) {
            Aggregation aggregation = Aggregation.newAggregation(
                // 4.过滤阶段:传入构建好的条件对象
                Aggregation.match(criteria),
                // 5.分组阶段:
                // 1:按food.grade分组
                // 2:$$ROOT代表引用原文档,并放入pandas数组
                Aggregation.group("food.grade").push("$$ROOT").as("pandas"),
                // 6.拆分阶段:
                // 将前面每个分组的pandas数组拆成一个个文档(index:下标,true:防丢失)
                Aggregation.unwind("pandas", "index", true),
                // 7.投影阶段:
                // 去掉部分字段不显示,只显示_id(原food.grade)、name、age、index字段
                Aggregation.project("_id", "pandas.name", "pandas.age", "index"),
                // 8.分页阶段:使用skip、limit来区分读取不同页的数据
                Aggregation.skip((long) (pageNumber - 1) * pageNumber),
                Aggregation.limit(pageSize)
            );

            AggregationResults<Map> results = mongoTemplate.aggregate(aggregation, "animals", Map.class);
            ans.add(results);
        }
        return ans;
    }
}

嵌套文档操作(update.push & update.pull)

// ========== 插入嵌套文档示例 ============
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import com.mongodb.client.model.Projections;
 
public class NestedDocumentService {
 
    @Autowired
    private MongoTemplate mongoTemplate;
 
    public void insertNestedDocument(String parentCollectionName, String parentId, String nestedCollectionName, Object nestedDocument) {
        // 获取父文档
        Query query = new Query(Criteria.where("_id").is(parentId));
        query.fields().include("_id");
        Object parentDocument = mongoTemplate.findOne(query, parentCollectionName);
 
        // 确保父文档存在
        if (parentDocument != null) {
            // 创建嵌套文档的字段路径
            String nestedFieldPath = nestedCollectionName + ".$";
 
            // 创建更新对象,并使用push操作符插入嵌套文档
            Update update = new Update();
            update.push(nestedFieldPath, nestedDocument);
 
            // 更新父文档
            mongoTemplate.updateFirst(query, update, parentCollectionName);
        }
    }
}
// ========== 删除嵌套文档示例 ============
public void deleteNestedDocument(String parentCollectionName, String parentId, String nestedCollectionName, Object nestedId) {
    // 获取父文档
    Query query = new Query(Criteria.where("_id").is(parentId));
    query.fields().include("_id");
    Object parentDocument = mongoTemplate.findOne(query, parentCollectionName);

    // 确保父文档存在
    if (parentDocument != null) {
        // 创建嵌套文档的字段路径
        String nestedFieldPath = nestedCollectionName + ".$";

        // 创建更新对象,并使用pull操作符删除嵌套文档
        Update update = new Update();
        update.pull(nestedFieldPath, nestedId);

        // 更新父文档
        mongoTemplate.updateFirst(query, update, parentCollectionName);
    }
}
}

对于嵌套文档的更新:

假设有一个名为users的集合,其中的文档包含嵌套的address字段,我们想要更新一个特定用户的地址。

@Autowired
private MongoTemplate mongoTemplate;

public void updateUserAddress(String userId, String street, String city) {
    Query query = new Query(Criteria.where("_id").is(userId));
    Update update = new Update();
    // 使用点表示法访问嵌套文档的字段
    update.set("address.street", street);
    update.set("address.city", city);

    // 更新第一个匹配的文档
    mongoTemplate.updateFirst(query, update, "users");
}

九:ExampleMatcher匹配器:了解

package com.cui.springmongodemo.service;

import com.cui.springmongodemo.entity.Animals;

import java.util.List;

/**
 * <p>
 * 功能描述:ExampleMatcher相关操作介绍
 * </p>
 *
 * @author cui haida
 * @date 2023/11/26/7:32
 */
public interface ExampleMatcherService {
    List<Animals> findAnimalsByName(Animals animals);
}
package com.cui.springmongodemo.service.impl;

import com.cui.springmongodemo.entity.Animals;
import com.cui.springmongodemo.service.ExampleMatcherService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * <p>
 * 功能描述:ExampleMatcher匹配器测试
 * --------- ExampleMatcher提供了三个方法用于创建匹配器:
 * // 创建一个默认的ExampleMatcher实例(底层调用了matchingAll()方法)
 * static ExampleMatcher matching();
 * // 创建一个匹配所有属性(字段)的ExampleMatcher实例
 * static ExampleMatcher matchingAll();
 * // 创建一个匹配任意属性(字段)的ExampleMatcher实例
 * static ExampleMatcher matchingAny();
 * 
 * ---------- 自定义匹配规则的方法:
 * // 为指定字段设置自定义的匹配规则
 * ExampleMatcher withMatcher(String propertyPath, GenericPropertyMatcher genericPropertyMatcher);
 * // 为字符串设置自定义的匹配规则
 * ExampleMatcher withStringMatcher(StringMatcher defaultStringMatcher);
 * // 设置匹配时忽略大小写
 * ExampleMatcher withIgnoreCase();
 * // 设置匹配时包含空值字段
 * ExampleMatcher withIncludeNullValues();
 * // 设置匹配时忽略空值字段
 * ExampleMatcher withIgnoreNullValues();
 * // 为指定字段设置“字段值转换器”
 * ExampleMatcher withTransformer(String propertyPath, PropertyValueTransformer propertyValueTransformer);
 * // 设置匹配时要排除的字段值
 * ExampleMatcher withIgnorePaths(String... ignoredPaths);
 * 
 * ------------ GenericPropertyMatcher类型,而该类中提供了一些内置规则方法
 * // 设置匹配时,指定字段的值,必须包含给定值
 * public GenericPropertyMatcher contains();
 * // 设置匹配时,指定字段的值,必须以给定值开头
 * public GenericPropertyMatcher startsWith();
 * // 设置匹配时,指定字段的值,必须以给定值结尾
 * public GenericPropertyMatcher endsWith();
 * // 设置匹配时,指定字段的值,必须与给定值完全匹配
 * public GenericPropertyMatcher exact();
 * // 设置匹配时,指定字段的值,必须符合给定的正则表达式
 * public GenericPropertyMatcher regex();
 * // 设置匹配时,指定字段的值会区分大小写
 * public GenericPropertyMatcher caseSensitive();
 * // 设置匹配时,指定字段的值不区分大小写
 * public GenericPropertyMatcher ignoreCase();
 *
 * </p>
 *
 * @author cui haida
 * @date 2023/11/26/7:34
 */
@Service
@Slf4j
public class ExampleMatcherServiceImpl implements ExampleMatcherService {

    @Resource
    private MongoTemplate mongoTemplate;

    @Override
    public List<Animals> findAnimalsByName(Animals animals) {
        // 创建了一个匹配器
        ExampleMatcher matcher = ExampleMatcher.matching()
                // 匹配规则一:忽略大小写
                .withIgnoreCase()
                // 匹配规则二:匹配字段:name字段,匹配关系:包含关系
                .withMatcher("name", ExampleMatcher.GenericPropertyMatchers.contains());
        // matcher + 参数 -> 创建出匹配器实例
        Example<Animals> example = Example.of(animals, matcher);
        // 将其传入到条件对象中
        Criteria criteria = new Criteria().alike(example);
        // mongoTemplate
        return mongoTemplate.find(Query.query(criteria), Animals.class);
    }
}

3:索引操作

一:创建索引(indexOps + ensureIndex)

// 在name字段创建一个唯一正序索引
Index index = new Index().on("name", Sort.Direction.ASC).unique();
// ensureIndex
mongoTemplate.indexOps("animals").ensureIndex(index);

二:查询索引(indexOps + getIndexInfo)

public List<IndexInfo> getIndexInfo(String collectionName) {
    return mongoTemplate.indexOps(collectionName).getIndexInfo();
}

三:删除索引(indexOps + dropIndex)

// 删除指定的索引
mongoTemplate.getCollection(COLLECTION_NAME).dropIndex(indexName);
// 删除指定的索引方式2
mongoTemplate.indexOps(COLLECTION_NAME).dropIndex(indexName);

// 删除全部的索引
mongoTemplate.getCollection(COLLECTION_NAME).dropIndexes();

四:基础项目演示

项目结构如下

在这里插入图片描述
依赖导入和配置,实体类在上面已经给出。

1:repository介绍

package com.cui.mongo_demo.service;

import com.cui.mongo_demo.entity.model.Animal;
import org.springframework.data.domain.Page;

import java.util.List;

/**
 * service层接口
 * @author cui haida
 * 2025/1/27
 */
public interface AnimalService {
    /**
     * 新增保存动物
     * @param animal 动物对象
     */
    void save(Animal animal);

    /**
     * 通过id删除
     * @param id 要删除的对象的id
     */
    void deleteById(Integer id);

    /**
     * 更新动物
     * @param animal 要更新的动物
     */
    void update(Animal animal);

    /**
     * 通过id返回指定的动物
     * @param id 要查询的动物的id
     * @return 查询到的动物对象
     */
    Animal findById(Integer id);

    /**
     * 拿到集合中的所有的动物对象
     * @return 所有的动物对象
     */
    List<Animal> findAll();

    /**
     * 查询年龄小于指定岁数的前三条数据
     * @param age 年龄
     * @return 年龄小于指定岁数的前三条数据
     */
    List<Animal> findTop3(int age);

    /**
     * 分页测试
     * @param pageNumber 页码
     * @param pageSize 页大小
     * @return 分页结果
     */
    Page<Animal> findByAgeNotNull(int pageNumber, int pageSize);

    /**
     * 自定义注解测试
     * @param age 年龄
     * @return 返回 < 输入年龄的动物集合
     */
    List<Animal> queryByDefine(int age);

    /**
     * 事务测试
     */
    void mongoTransaction();
}
package com.cui.mongo_demo.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.cui.mongo_demo.entity.model.Animal;
import com.cui.mongo_demo.repository.AnimalRepository;
import com.cui.mongo_demo.service.AnimalService;
import com.mongodb.client.ClientSession;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import org.bson.Document;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;
import java.util.Optional;

/**
 * @author cui haida
 * 2025/1/27
 */
@Service
public class AnimalServiceImpl implements AnimalService {

    @Resource
    private AnimalRepository animalRepository;

    @Resource
    private MongoClient mongoClient;

    @Override
    public void save(Animal animal) {
        animalRepository.save(animal);
    }

    @Override
    public void deleteById(Integer id) {
        animalRepository.deleteById(id);
    }

    @Override
    public void update(Animal animal) {
        animalRepository.save(animal);
    }

    @Override
    public Animal findById(Integer id) {
        Optional<Animal> animal = animalRepository.findById(id);
        return animal.orElse(null);
    }

    @Override
    public List<Animal> findAll() {
        return animalRepository.findAll();
    }

    @Override
    public List<Animal> findTop3(int age) {
        return animalRepository.findTop3ByAgeLessThan(age);
    }

    @Override
    public Page<Animal> findByAgeNotNull(int pageNumber, int pageSize) {
        PageRequest pageRequest = PageRequest.of(pageNumber, pageSize);
        return animalRepository.findByAgeNotNull(pageRequest);
    }

    @Override
    public List<Animal> queryByDefine(int age) {
        return animalRepository.queryByDefine(age);
    }

    @Override
    public void mongoTransaction() {
        // 1.先通过mongoClient开启一个session会话
        ClientSession session = mongoClient.startSession();
        try{
            // 2.通过session开启事务
            session.startTransaction();

            // 3.创建一个实体对象
            Animal animals = new Animal();
            animals.setId(222);
            animals.setName("白白");
            animals.setColor("白色");
            animals.setAge(1);

            // 4.通过mongoClient获取集合对象
            MongoCollection<Document> collection = mongoClient.getDatabase("test").getCollection("animals");

            // 5.通过集合对象提供的insert方法插入数据
            collection.insertOne(
                    session,
                    Document.parse(JSONObject.toJSONString(animals))
            );

            // 6.模拟执行异常
            int n = 100 / 0;

            // 7.如果执行到这里,说明执行没报错,提交事务
            session.commitTransaction();
        } catch (Exception e) {
            // 8.如果进入了catch,说明出现异常,回滚事务
            session.abortTransaction();
            e.printStackTrace();
        }
        // 9.关闭前面开启的session会话
        session.close();
    }
}

2:template介绍

package com.cui.mongo_demo.service;

import com.cui.mongo_demo.entity.model.Animal;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.index.IndexInfo;

import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author cui haida
 * 2025/1/27
 */
public interface TemplateService {

    /**
     * 创建集合
     *
     * @param collectionName 集合名称
     */
    public void createCollection(String collectionName);

    /**
     * 创建集合并设置固定大小
     *
     * @param collectionName 集合名称
     * @param size           集合大小
     * @param maxDocCount    最大集合文档数量
     * @return 集合创建结果
     */
    public Integer createCollectionFixedSize(String collectionName, Long size, Long maxDocCount);


    /**
     * 获取所有集合名称
     *
     * @return 集合名称列表
     */
    public Set<String> getAllCollectionNames();


    /**
     * 判断集合是否存在
     *
     * @param collectionName 集合名称
     * @return 集合是否存在
     */
    public boolean existCollectionByName(String collectionName);


    /**
     * 删除集合
     *
     * @param collectionName 集合名称
     */
    public void dropCollection(String collectionName);

    /**
     * 插入文档
     *
     * @param animal 文档
     * @return 插入结果
     */
    public Animal insertDocument(Animal animal);

    /**
     * 批量插入文档
     *
     * @param animals 文档列表
     * @return 插入结果
     */
    public List<Animal> batchInsertDocument(List<Animal> animals);

    /**
     * 保存文档
     *
     * @param animal 文档
     * @return 保存结果
     */
    public Animal saveAnimal(Animal animal);

    /**
     * 查询所有文档
     *
     * @return 文档列表
     */
    public List<Animal> findAll();

    /**
     * 根据id查询
     *
     * @param id 文档id
     * @return 文档
     */
    public Animal findById(Integer id);

    /**
     * 根据条件查询
     *
     * @param sex 性别
     * @param age 年龄
     * @return 文档列表
     */
    public List<Animal> findByCondition(String sex, Integer age);

    /**
     * 根据年龄更新
     *
     * @param oldAge 旧年龄
     * @param newAge 新年龄
     * @param newName 新名字
     */
    public void updateByAge(Integer oldAge, Integer newAge, String newName);

    /**
     * 根据年龄和性别删除
     *
     * @param age 年龄
     * @param sex 性别
     */
    public void removeByAgeAndSex(Integer age, String sex);

    /**
     * 分页查询
     *
     * @param age    年龄
     * @param pageNum 页码
     * @param pageSize 页大小
     * @return 文档列表
     */
    public List<Animal> pageByAge(Integer age, Integer pageNum, Integer pageSize);

    /**
     * 聚合查询
     *
     * @return 聚合结果
     */
    public AggregationResults<Map> aggByAgeAndCount();

    /**
     * 聚合查询
     *
     * @return 聚合结果
     */
    public AggregationResults<Map> aggOpMinAndMax();

    /**
     * 聚合查询
     *
     * @return 聚合结果
     */
    public AggregationResults<Map> aggOpComplex(int bottomAge);

    /**
     * 聚合查询
     *
     * @return 聚合结果
     */
    public List<AggregationResults<Map>> aggOpComplexOfPage(int bottomAge);


    /**
     * 插入嵌套文档
     *
     * @param parentCollectionName 父集合名称
     * @param parentId 父文档id
     * @param nestedCollectionName 嵌套集合名称
     * @param nestedDocument 嵌套文档
     */
    public void insertNestedDocument(String parentCollectionName, String parentId, String nestedCollectionName, Object nestedDocument);

    /**
     * 删除嵌套文档
     *
     * @param parentCollectionName 父集合名称
     * @param parentId 父文档id
     * @param nestedCollectionName 嵌套集合名称
     * @param nestedId 嵌套文档id
     */
    public void deleteNestedDocument(String parentCollectionName, String parentId, String nestedCollectionName, Object nestedId);


    /**
     * 创建索引
     * @param collectionName 集合名称
     */
    public void createIndex(String collectionName, String fieldName);

    /**
     * 获取索引信息
     * @param collectionName 集合名称
     * @return 索引信息
     */
    public List<IndexInfo> getIndexInfo(String collectionName);

    /**
     * 删除索引
     * @param collectionName 集合名称
     * @param fieldName 索引对应的字段的名称
     */
    public void removeIndex(String collectionName, String fieldName);

    /**
     * 删除所有索引
     * @param collectionName 集合名称
     */
    public void removeAllIndex(String collectionName);
}
package com.cui.mongo_demo.service.impl;

import com.cui.mongo_demo.entity.model.Animal;
import com.cui.mongo_demo.service.TemplateService;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.CollectionOptions;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.index.Index;
import org.springframework.data.mongodb.core.index.IndexInfo;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * @author cui haida
 * 2025/1/27
 */
@Service
public class TemplateServiceImpl implements TemplateService {

    private final String COLLECTION_NAME = "animal";

    private final MongoTemplate mongoTemplate;

    public TemplateServiceImpl(MongoTemplate mongoTemplate) {
        this.mongoTemplate = mongoTemplate;
    }

    @Override
    public void createCollection(String collectionName) {
        mongoTemplate.createCollection(collectionName);
    }

    @Override
    public Integer createCollectionFixedSize(String collectionName, Long size, Long maxDocCount) {
        // .capped() // 创建固定集合。固定集合是指有着固定大小的集合,当达到最大值时,它会自动覆盖最早的文档
        // .size(size) // 固定集合指定一个最大值,以千字节计(KB),如果 capped 为 true,也需要指定该字段
        // .maxDocuments(maxDocCount); // 指定固定集合中包含文档的最大数量
        CollectionOptions collectionOptions = CollectionOptions.empty()
                .capped()
                .size(size)
                .maxDocuments(maxDocCount);
        mongoTemplate.createCollection(collectionName, collectionOptions);
        return mongoTemplate.collectionExists(collectionName) ? 1 : 0;
    }

    @Override
    public Set<String> getAllCollectionNames() {
        return mongoTemplate.getCollectionNames();
    }

    @Override
    public boolean existCollectionByName(String collectionName) {
        return mongoTemplate.collectionExists(collectionName);
    }

    @Override
    public void dropCollection(String collectionName) {
        mongoTemplate.getCollection(collectionName).drop();
    }

    @Override
    public Animal insertDocument(Animal animal) {
        return mongoTemplate.insert(animal, COLLECTION_NAME);
    }

    @Override
    public List<Animal> batchInsertDocument(List<Animal> animals) {
        Collection<Animal> as = mongoTemplate.insert(animals, COLLECTION_NAME);
        return new ArrayList<>(as);
    }

    @Override
    public Animal saveAnimal(Animal animal) {
        return mongoTemplate.save(animal, COLLECTION_NAME);
    }

    @Override
    public List<Animal> findAll() {
        return mongoTemplate.findAll(Animal.class, COLLECTION_NAME);
    }

    @Override
    public Animal findById(Integer id) {
        return mongoTemplate.findById(id, Animal.class, COLLECTION_NAME);
    }

    @Override
    public List<Animal> findByCondition(String sex, Integer age) {
        // 1:构建条件-准则
        Criteria sexOfCriteria = Criteria.where("sex").is(sex);
        Criteria ageOfCriteria = Criteria.where("age").is(age);
        // 指定两个准则的拼接条件为and, 也就是 sex = ${sex} and age = ${age}
        Criteria criteria = new Criteria().andOperator(sexOfCriteria, ageOfCriteria);
        // 2:基于准则构建查询条件
        Query query = new Query(criteria);
        // 3:通过find方法查询数据,返回List查询结果
        return mongoTemplate.find(query, Animal.class, COLLECTION_NAME);
    }

    @Override
    public void updateByAge(Integer oldAge, Integer newAge, String newName) {
        // 1:指定更新准则
        Criteria criteria = Criteria.where("age").is(oldAge);
        Query query = new Query(criteria);
        // 2:指定更新内容
        Update update = new Update();
        update.set("age", newAge);
        update.set("name", newName);
        // 3:通过updateMulti方法更新数据, 还有updateFirst和upsert
        mongoTemplate.updateMulti(query, update, Animal.class, COLLECTION_NAME);
    }

    @Override
    public void removeByAgeAndSex(Integer age, String sex) {
        // 1:指定删除条件
        Criteria criteria = Criteria.where("age").is(age).and("sex").is(sex);
        Query query = new Query(criteria);
        mongoTemplate.remove(query, Animal.class, COLLECTION_NAME);
    }

    @Override
    public List<Animal> pageByAge(Integer age, Integer pageNum, Integer pageSize) {
        // 1:构建查询条件
        Criteria criteria = Criteria.where("age").is(age);
        Query query = new Query(criteria);
        // 加入分页条件, 并通过年龄进行倒排(先进行倒排再分页)
        query.with(PageRequest.of(pageNum, pageSize, Sort.Direction.DESC, "age"));
        // 2:通过find方法查询数据,返回List查询结果
        return mongoTemplate.find(query, Animal.class, COLLECTION_NAME);
    }

    @Override
    public AggregationResults<Map> aggByAgeAndCount() {
        // 1. 构建聚合信息
        Aggregation aggregation = Aggregation.newAggregation(
                // 基于年龄字段分组,接着统计每组数量,并为统计字段取别名
                Aggregation.group("age")
                        .count().as("count"),
                // 基于分组后的_id字段(原age)字段排序(升序)
                Aggregation.sort(Sort.Direction.ASC, "_id")
        );
        // 2. 执行聚合操作,返回结果, 返回结果为Map类型
        // 第一个参数是聚合条件,第二个参数是作用的集合名称,第三个参数是返回类型
        return mongoTemplate.aggregate(aggregation, "animals", Map.class);
    }

    @Override
    public AggregationResults<Map> aggOpMinAndMax() {
        // 1. 构建聚合条件 Aggregation.newAggregation( condition1, condition2...)
        Aggregation aggregation = Aggregation.newAggregation(
                // 通过颜色进行分组,然后统计最大值、最小值、平均值。
                Aggregation.group("color")
                        .max("age").as("max_age")
                        .min("age").as("min_age")
                        .avg("age").as("avg_age"),
                // 排序,按照最大值进行倒排
                Aggregation.sort(Sort.Direction.DESC, "max_age")
        );
        // 2. 执行聚合操作,返回结果
        return mongoTemplate.aggregate(aggregation, "animals", Map.class);
    }

    @Override
    public AggregationResults<Map> aggOpComplex(int bottomAge) {
        // 1. 过滤掉food为空,以及age小于3岁的数据的条件
        Criteria criteria =
                Criteria.where("food").exists(true).and("age").gte(3);

        // 2. 构建聚合条件
        Aggregation aggregation = Aggregation.newAggregation(
                // 1:match先进行过滤
                Aggregation.match(criteria),
                // 2:分组 + 聚合函数
                Aggregation.group("food.grade")
                        // 通过food.grade进行分组,然后统计每一个分组的信息
                        .avg("age").as("avg_age")
                        .first("name").as("first_name")
                        .last("name").as("last_name")
                        .push("name").as("names"),
                // 排序
                Aggregation.sort(Sort.Direction.ASC, "age")
        );

        // 3. 执行聚合操作,返回结果
        return mongoTemplate.aggregate(aggregation, "animals", Map.class);
    }

    @Override
    public List<AggregationResults<Map>> aggOpComplexOfPage(int bottomAge) {
        // 1.每页的数据量为2条
        int pageSize = 2;
        // 2.过滤掉food字段为空的条件对象
        Criteria criteria = Criteria.where("food").exists(true);
        // 初始化存储结果的数据结构
        List<AggregationResults<Map>> ans = new ArrayList<>();

        // 3.用for循环模拟分页查询
        for (int pageNumber = 1; pageNumber <= 3; pageNumber++) {
            Aggregation aggregation = Aggregation.newAggregation(
                    // 4.过滤阶段:传入构建好的条件对象
                    Aggregation.match(criteria),
                    // 5.分组阶段:
                    // 1:按food.grade分组
                    // 2:$$ROOT代表引用原文档,并放入pandas数组(// 将源文档信息推入(push...as)pandas数组)
                    Aggregation.group("food.grade")
                            .push("$$ROOT").as("pandas"),
                    // 6.拆分阶段:
                    // 将前面每个分组的pandas数组拆成一个个文档(index:下标,true:防丢失)
                    Aggregation.unwind("pandas", "index", true),
                    // 7.投影阶段:
                    // 去掉部分字段不显示,只显示_id(原food.grade)、name、age、index字段
                    Aggregation.project("_id", "pandas.name", "pandas.age", "index"),
                    // 8.分页阶段:使用skip、limit来区分读取不同页的数据
                    Aggregation.skip((long) (pageNumber - 1) * pageNumber),
                    Aggregation.limit(pageSize)
            );

            AggregationResults<Map> results = mongoTemplate.aggregate(aggregation, "animals", Map.class);
            ans.add(results);
        }
        return ans;
    }

    @Override
    public void insertNestedDocument(String parentCollectionName, String parentId, String nestedCollectionName, Object nestedDocument) {
        // 1. 获取父文档
        Query query = new Query(Criteria.where("_id").is(parentId));
        query.fields().include("_id");
        Animal parentDocument = mongoTemplate.findOne(query, Animal.class, parentCollectionName);

        if (parentDocument != null) {
            // 2.如果父文档存在,则插入嵌套文档
            String nestedFieldPath = nestedCollectionName + ".$";

            // 3.创建更新对象,并使用push操作符插入嵌套文档
            Update update = new Update();
            update.push(nestedFieldPath, nestedDocument);

            // 4.更新父文档
            mongoTemplate.updateFirst(query, update, parentCollectionName);
        }
    }

    @Override
    public void deleteNestedDocument(String parentCollectionName, String parentId, String nestedCollectionName, Object nestedId) {
        // 1. 获取父文档
        Query query = new Query(Criteria.where("_id").is(parentId));
        query.fields().include("_id");
        Animal parentDocument = mongoTemplate.findOne(query, Animal.class, parentCollectionName);

        if (parentDocument != null) {
            // 2.如果父文档存在,则删除嵌套文档
            String nestedFieldPath = nestedCollectionName + ".$." + "_id";

            // 3.创建更新对象,并使用pull操作符删除嵌套文档
            Update update = new Update();
            update.pull(nestedFieldPath, nestedId);

            // 4.更新父文档
            mongoTemplate.updateFirst(query, update, parentCollectionName);
        }
    }

    @Override
    public void createIndex(String collectionName, String fieldName) {
        // 1.创建索引信息
        Index index = new Index()
                // 作用在指定字段上,排序方式为正序
                .on(fieldName, Sort.Direction.ASC)
                // 是唯一索引
                .unique();

        // 2.indexOps()方法用于获取集合的索引操作对象,然后调用ensureIndex()方法来创建索引
        mongoTemplate.indexOps(collectionName).ensureIndex(index);
    }

    @Override
    public List<IndexInfo> getIndexInfo(String collectionName) {
        return mongoTemplate.indexOps(collectionName).getIndexInfo();
    }

    @Override
    public void removeIndex(String collectionName, String fieldName) {
        // 删除指定字段上的索引
        mongoTemplate.indexOps(collectionName).dropIndex(fieldName);
    }

    @Override
    public void removeAllIndex(String collectionName) {
        mongoTemplate.indexOps(collectionName).dropAllIndexes();
    }
}

3:ExampleMatcher介绍

package com.cui.mongo_demo.service;

import com.cui.mongo_demo.entity.model.Animal;

import java.util.List;

/**
 * @author cuihaida
 * 2025/1/28
 */
public interface ExampleMatcherService {
    /**
     * 根据名称查询
     * @param animal 查询条件
     * @return 查询结果
     */
    List<Animal> findAnimalsByName(Animal animal);
}
package com.cui.mongo_demo.service.impl;

import com.cui.mongo_demo.entity.model.Animal;
import com.cui.mongo_demo.service.ExampleMatcherService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * <p>
 *  * 功能描述:ExampleMatcher匹配器测试
 *  * --------- ExampleMatcher提供了三个方法用于创建匹配器:
 *  * // 创建一个默认的ExampleMatcher实例(底层调用了matchingAll()方法)
 *  * static ExampleMatcher matching();
 *  * // 创建一个匹配所有属性(字段)的ExampleMatcher实例
 *  * static ExampleMatcher matchingAll();
 *  * // 创建一个匹配任意属性(字段)的ExampleMatcher实例
 *  * static ExampleMatcher matchingAny();
 *  *
 *  * ---------- 自定义匹配规则的方法:
 *  * // 为指定字段设置自定义的匹配规则
 *  * ExampleMatcher withMatcher(String propertyPath, GenericPropertyMatcher genericPropertyMatcher);
 *  * // 为字符串设置自定义的匹配规则
 *  * ExampleMatcher withStringMatcher(StringMatcher defaultStringMatcher);
 *  * // 设置匹配时忽略大小写
 *  * ExampleMatcher withIgnoreCase();
 *  * // 设置匹配时包含空值字段
 *  * ExampleMatcher withIncludeNullValues();
 *  * // 设置匹配时忽略空值字段
 *  * ExampleMatcher withIgnoreNullValues();
 *  * // 为指定字段设置“字段值转换器”
 *  * ExampleMatcher withTransformer(String propertyPath, PropertyValueTransformer propertyValueTransformer);
 *  * // 设置匹配时要排除的字段值
 *  * ExampleMatcher withIgnorePaths(String... ignoredPaths);
 *  *
 *  * ------------ GenericPropertyMatcher类型,而该类中提供了一些内置规则方法
 *  * // 设置匹配时,指定字段的值,必须包含给定值
 *  * public GenericPropertyMatcher contains();
 *  * // 设置匹配时,指定字段的值,必须以给定值开头
 *  * public GenericPropertyMatcher startsWith();
 *  * // 设置匹配时,指定字段的值,必须以给定值结尾
 *  * public GenericPropertyMatcher endsWith();
 *  * // 设置匹配时,指定字段的值,必须与给定值完全匹配
 *  * public GenericPropertyMatcher exact();
 *  * // 设置匹配时,指定字段的值,必须符合给定的正则表达式
 *  * public GenericPropertyMatcher regex();
 *  * // 设置匹配时,指定字段的值会区分大小写
 *  * public GenericPropertyMatcher caseSensitive();
 *  * // 设置匹配时,指定字段的值不区分大小写
 *  * public GenericPropertyMatcher ignoreCase();
 *  </p>
 * @author cui haida
 * 2025/1/28
 */
@Service
@Slf4j
public class ExampleMatcherServiceImpl implements ExampleMatcherService {

    private final MongoTemplate mongoTemplate;

    public ExampleMatcherServiceImpl(MongoTemplate mongoTemplate) {
        this.mongoTemplate = mongoTemplate;
    }

    @Override
    public List<Animal> findAnimalsByName(Animal animal) {
        // 创建了一个匹配器
        ExampleMatcher matcher = ExampleMatcher.matching()
                // 匹配规则一:忽略大小写
                .withIgnoreCase()
                // 匹配规则二:匹配字段:name字段,匹配关系:包含关系
                .withMatcher("name", ExampleMatcher.GenericPropertyMatchers.contains());
        // matcher + 参数 -> 创建出匹配器实例
        Example<Animal> example = Example.of(animal, matcher);
        // 将其传入到条件对象中
        Criteria criteria = new Criteria().alike(example);
        // mongoTemplate
        return mongoTemplate.find(Query.query(criteria), Animal.class);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/961302.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringCloud系列教程:微服务的未来(十八)雪崩问题、服务保护方案、Sentinel快速入门

前言 在分布式系统中&#xff0c;雪崩效应&#xff08;Avalanche Effect&#xff09;是一种常见的故障现象&#xff0c;通常发生在系统中某个组件出现故障时&#xff0c;导致其他组件级联失败&#xff0c;最终引发整个系统的崩溃。为了有效应对雪崩效应&#xff0c;服务保护方…

Golang Gin系列-7:认证和授权

在本章中&#xff0c;我们将探讨Gin框架中身份验证和授权的基本方面。这包括实现基本的和基于令牌的身份验证&#xff0c;使用基于角色的访问控制&#xff0c;应用中间件进行授权&#xff0c;以及使用HTTPS和漏洞防护保护应用程序。 实现身份认证 Basic 认证 Basic 认证是内置…

C++ 中用于控制输出格式的操纵符——setw 、setfill、setprecision、fixed

目录 四种操纵符简要介绍 setprecision基本用法 setfill的基本用法 fixed的基本用法 setw基本用法 以下是一些常见的用法和示例&#xff1a; 1. 设置字段宽度和填充字符 2. 设置字段宽度和对齐方式 3. 设置字段宽度和精度 4. 设置字段宽度和填充字符&#xff0c;结合…

06_改善播放效果--优先级与阻塞

一、声明 本文章的程序是基于05篇的程序改善的&#xff0c;所以CubeMx的配置看05篇的就好 且此篇文章由于红外遥控暂时未使用&#xff0c;还不知其是否能成功显示现象 二、keil5代码 这个优先级要1&#xff0c;但是如果只是这个优先级1的话&#xff0c;那么我的LED灯闪烁的任务…

【Rust自学】16.3. 共享状态的并发

喜欢的话别忘了点赞、收藏加关注哦&#xff08;加关注即可阅读全文&#xff09;&#xff0c;对接下来的教程有兴趣的可以关注专栏。谢谢喵&#xff01;(&#xff65;ω&#xff65;) 16.3.1. 使用共享来实现并发 还记得Go语言有一句名言是这么说的&#xff1a;Do not commun…

项目集成Nacos

文章目录 1.环境搭建1.创建模块 sunrays-common-cloud-nacos-starter2.目录结构3.pom.xml4.自动配置1.NacosAutoConfiguration.java2.spring.factories 5.引入cloud模块通用依赖 2.测试1.创建模块 sunrays-common-cloud-nacos-starter-demo2.目录结构3.pom.xml4.application.ym…

计算机网络 (59)无线个人区域网WPAN

前言 无线个人区域网&#xff08;WPAN&#xff0c;Wireless Personal Area Network&#xff09;是一种以个人为中心&#xff0c;采用无线连接方式的个人局域网。 一、定义与特点 定义&#xff1a;WPAN是以个人为中心&#xff0c;实现活动半径小、业务类型丰富、面向特定群体的无…

Python 之 Excel 表格常用操作

示例文件 test.xlsx 将各个表单拆分成单独的 Excel 文件 import os.pathimport openpyxl import pandasdef handle_excel(file_path):dirname os.path.dirname(file_path)basename os.path.basename(file_path).split(".")[0]wb openpyxl.load_workbook(file_pat…

01学习预热篇(D6_正式踏入JVM深入学习前的铺垫)

目录 学习前言 一、虚拟机的结构 1. Java虚拟机参数设置 2. java 堆 3. 出入栈 4. 局部变量表 1> 局部变量的剖析 2> 局部变量的回收 5. 操作数栈 1> 常量入栈指令 2> 局部变量值转载到栈中指令 3> 将栈顶值保存到局部变量中指令 6. 帧数据区 7. 栈…

知识库建设对提升团队协作与创新能力的影响分析

内容概要 在当今快速变革的商业环境中&#xff0c;知识库建设的重要性愈发凸显。它不仅是信息存储的载体&#xff0c;更是推动组织内部沟通与协作的基石。通过系统整理与管理企业知识&#xff0c;团队成员能够便捷地访问相关信息&#xff0c;使得协作过程更为流畅&#xff0c;…

C语言字符串详解

1. C语言中的字符串基础 C语言中的字符串是程序设计中不可忽视的部分。与现代高级编程语言不同&#xff0c;C语言对字符串的处理方式直接、灵活&#xff0c;并且强大。在C语言中&#xff0c;字符串并不是一种特殊的数据类型&#xff0c;而是字符数组的一种表现形式。字符串通常…

神经网络的通俗介绍

人工神经网络&#xff0c;是一种模仿人类大脑工作原理的数学模型。人类的大脑是由无数的小“工作站”组成的&#xff0c;每个工作站叫做“神经元”。这些神经元通过“电线”互相连接&#xff0c;负责接收、处理和传递信息。 一、人类大脑神经网络 人类大脑的神经网络大概长这…

SpringBoot中Excel表的导入、导出功能的实现

文章目录 一、easyExcel简介二、Excel表的导出2.1 添加 Maven 依赖2.2 创建导出数据的实体类4. 编写导出接口5. 前端代码6. 实现效果 三、excel表的导出1. Excel表导入的整体流程1.1 配置文件存储路径 2. 前端实现2.1 文件上传组件 2.2 文件上传逻辑3. 后端实现3.1 文件上传接口…

vim交换文件的工作原理

在vim中&#xff0c;交换文件是一个临时文件&#xff0c;当我们使用vim打开一个文件进行编辑&#xff08;一定得是做出了修改才会产生交换文件&#xff09;时候&#xff0c;vim就会自动创建一个交换文件&#xff0c;而之后我们对于文件的一系列修改都是在交换文件中进行的&…

C++/stack_queue

目录 1.stack 1.1stack的介绍 1.2stack的使用 练习题&#xff1a; 1.3stack的模拟实现 2.queue的介绍和使用 2.1queue的介绍 2.2queue的使用 2.3queue的模拟实现 3.priority_queue的介绍和使用 3.1priority_queue的介绍 3.2priority_queue的使用 欢迎 1.stack 1.1stack…

5分钟带你获取deepseek api并搭建简易问答应用

目录 1、获取api 2、获取base_url和chat_model 3、配置模型参数 方法一&#xff1a;终端中临时将加入 方法二&#xff1a;创建.env文件 4、 配置client 5、利用deepseek大模型实现简易问答 deepseek-v3是截止博文撰写之日&#xff0c;无论是国内还是国际上发布的大模型中…

机器学习day4

自定义数据集 使用pytorch框架实现逻辑回归并保存模型&#xff0c;然后保存模型后再加载模型进行预测 import numpy as np import torch import torch.nn as nn import torch.optim as optimizer import matplotlib.pyplot as pltclass1_points np.array([[2.1, 1.8],[1.9, 2…

58.界面参数传递给Command C#例子 WPF例子

界面参数的传递&#xff0c;界面参数是如何从前台传送到后台的。 param 参数是从界面传递到命令的。这个过程通常涉及以下几个步骤&#xff1a; 数据绑定&#xff1a;界面元素&#xff08;如按钮&#xff09;的 Command 属性绑定到视图模型中的 RelayCommand 实例。同时&#x…

Julius AI 人工智能数据分析工具介绍

Julius AI 是一款由 Casera Labs 开发的人工智能数据分析工具&#xff0c;旨在通过自然语言交互和强大的算法能力&#xff0c;帮助用户快速分析和可视化复杂数据。这款工具特别适合没有数据科学背景的用户&#xff0c;使数据分析变得简单高效。 核心功能 自然语言交互&#x…

【JavaEE进阶】应用分层

目录 &#x1f38b;序言 &#x1f343;什么是应用分层 &#x1f38d;为什么需要应用分层 &#x1f340;如何分层(三层架构) &#x1f384;MVC和三层架构的区别和联系 &#x1f333;什么是高内聚低耦合 &#x1f38b;序言 通过上⾯的练习,我们学习了SpringMVC简单功能的开…