Bootstrap

springboot整合mongodb

个人学习整理,欢迎批评指正,共同进步

image.png

依赖导入

主要依赖(必须)
        <!--        springboot-mongodb 依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>
辅助依赖(非必须)
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- mongodb 条件构建器-->
        <!-- 可以避免硬编码存在的魔法值问题-->
        <dependency>
            <groupId>com.gitee.xiezengcheng</groupId>
            <artifactId>fluent-mongo</artifactId>
            <version>1.1.1</version>
        </dependency>

配置文件编写

spring:
  data:
    mongodb:
      ## 实际连接的库
      database: webflux
      ## 地址
      host: 8.140.37.4
      ## 端口
      port: 27017
      ## 用户名
      username: root
      ## 密码,若报错则使用单引号包裹密码
      password: '123456'
      ## 认证库
      authentication-database: admin

集成 springboot

repository 接口集成
1-继承 MongoRepository<实体类,主键泛型> 接口
2-使用方法名联想或使用 @Query 注解实现自定义方法
3-编写方法

    /**
     * 实体类属性名和mongo集合字段名都可以使用
     * 若属性名和字段名不一致则要求实体属性使用 @Field 进行属性修饰,指定在集合中的字段名
     */

	//根据 id 查询
	@Query(value = "{'id': ?0}")
    TestEntity getTestEntityById(Long id);

	//根据 name 全模糊查询
    @Query(value = "{'username': /?0/ }")
    List<TestEntity> getAllTestTntityByNameLike(String name);

	//区间判断
    @Query(value = "{'$or':[{'age':{'$lt': ?0 }},{'age':{'$gt': ?1 }}]}")
    List<TestEntity> getTestEntityByIntervalOfExclusion(Integer a1, Integer a2);
4-使用
1-插入/修改
    /**
     * 插入/修改
     */
    @Test
    public void insert(){
        /**
         * dao.insert()
         * 传入实体类,若相同id存在则报错
         */
        TestEntity testEntity = new TestEntity()
                .setAge(110)
                .setName("长寿")
                .setIsDeleted(false)
                .setCreateTime(new Date())
                .setId(99L);
        TestEntity insert = dao.insert(testEntity);
        System.out.println(insert);

        /**
         * dao.save()
         * 相同id存在则修改,否则则插入
         * 存在问题:不指定值的属性会被置空
         */
        TestEntity save = dao.save(testEntity);
        System.out.println(save);
    }
2-查询
    /**
     * 简单查询
     */
    @Test
    public void simpleQuery(){
        //根据id查询,返回optional
        Optional<TestEntity> byId = dao.findById(10L);
        byId.ifPresent(System.out::println);
        //查询所有
        List<TestEntity> all = dao.findAll();
        //查询所有并传入分页和排序对象
        //page 从 0开始,0代表第一页
        PageRequest of = PageRequest.of(2, 3);
        PageRequest of1 = PageRequest.of(2, 10, Sort.by(Sort.Order.desc("age")));
        Page<TestEntity> all1 = dao.findAll(of);
//        Page<TestEntity> all2 = dao.findAll(of1);
        List<TestEntity> testEntities = all1.toList();
        System.out.println(testEntities);

    }

    public static void main(String[] args) {
        PageRequest of = PageRequest.of(2, 3, Sort.by(Sort.Order.desc("age")));
        System.out.println(of);
    }


    /**
     * 复杂查询
     */
    @Test
    public void complexQuery(){
        //构建条件匹配对象
        TestEntity testEntity = new TestEntity()
                .setAge(18)
                .setName("张三")
                .setIsDeleted(false);

        //构建条件匹配器
        ExampleMatcher name = ExampleMatcher.matching()
                //忽略条件,若不忽略则也会参与条件匹配,会导致无法查询到数据
                .withIgnorePaths("age","is_deleted","create_time","_id","_class")
                .withMatcher("username", ExampleMatcher.GenericPropertyMatchers.caseSensitive())
                .withMatcher("username",item -> item.contains());
        //组合匹配器和条件匹配对象
        Example<TestEntity> of = Example.of(testEntity, name);
        //传入 example 查询条件
        List<TestEntity> all = dao.findAll(of);
        System.out.println(all);
    }
3-删除
    /**
     * 删除
     */
    @Test
    public void remove(){
        /**
         * 使用id删除
         * 无返回值,成功失败都无返回值
         */
        dao.deleteById(99L);
    }
4-使用自定义接口
    /**
     * 使用注解实现手动编写 mognog 语句
     */
    @Test
    public void queryAnnoTest(){
        TestEntity testEntityById = dao.getTestEntityById(3L);
        System.out.println(testEntityById);

        List<TestEntity> entities = dao.getAllTestTntityByNameLike("张三");
        System.out.println(entities);

        List<TestEntity> testEntityByIntervalOfExclusion = dao.getTestEntityByIntervalOfExclusion(20, 60);
        System.out.println(testEntityByIntervalOfExclusion);

    }
使用 MongoTemplate
1.集合操作
        //创建集合,
        MongoCollection<Document> test_collection = mongoTemplate.createCollection("test_collection");
        //判断集合是否存在
        boolean test_collection1 = mongoTemplate.collectionExists("test_collection");
        //删除集合
        mongoTemplate.dropCollection("test_collection");
2-查询(find)
1-简单查询
        /**
         * 基本每个查询方法都有一个可以指定查询集合的重载方法
         */
        //查询所有
        List<TestEntity> all = mongoTemplate.findAll(TestEntity.class);
        System.out.println(all);
        //id查询
        TestEntity byId = mongoTemplate.findById(1, TestEntity.class);
        System.out.println(byId);
        //单个查询
        TestEntity one = mongoTemplate.findOne(new Query(), TestEntity.class);
        System.out.println(one);
2-构建查询对象(Query)
        /**
         * 构建查询对象
         */
        Query query = new Query();
        //传入 Sort 排序对象
        query.with(Sort.by(Sort.Order.desc("id")));
        //传入分页对象
        query.skip(10);
        query.limit(10);
        //构建查询条件
        Criteria name = new Criteria()
                .where("name")
                .is("张三");
        Criteria age = Criteria.where("age")
                .gt(18)
                .lt(60);
        //添加条件进查询对象
        query.addCriteria(name);
        query.addCriteria(age);
        //指定查询字段
        query.fields()
                //包含指定字段,不能和排除同时使用
                .include(getFieldName(TestEntity::getAge))
                //排除指定字段,不能和包含同时使用
                .exclude(getFieldName(TestEntity::getName));
        //执行查询
        List<TestEntity> entities = mongoTemplate.find(query, TestEntity.class);
3-and 和 or 操作
        //方法一: 使用 Criteria.andOperator() 进行多条件拼接,多用于嵌套条件拼接
    	//查询用户状态为 1 或者 用户年龄小于18和大于60的
        //如: select * from user where (user.age < 18 or user.age > 60) or user.status = 1  就可适用  Criteria.andOperator() 进行嵌套条件拼接
        //示例:
        //Criteria criteria = Criteria.where("status")
        //        .is(1)
        //        .orOperator(
        //                new Criteria()
        //                        .orOperator(
        //                                Criteria.where("age")
        //                                        .lt(18),
        //                                Criteria.where("age")
        //                                        .gt(65)
        //                        )
        //        );
        //System.out.println(criteria.getCriteriaObject());
        //使用 criteria 进行同级拼接则无法实现
        /**
         * and
         * 查询姓名中包含 张 和年龄大于等于 15的
         */
        List<TestEntity> andEntitys = mongoTemplate.find(new Query().addCriteria(
                new Criteria()
                        .andOperator(
                                Criteria.where("name")
                                        .regex("张"),
                                Criteria.where("age")
                                        .gte(15)
                        )
        ), TestEntity.class);
        Query query = new Query();
        System.out.println(andEntitys);
        //方法二: 同级拼接
        //如: select * from user where user.age > 18 and user.id > 220
        //仅适用于同级 and 拼接,不适用于 or 拼接,且无法进行多级条件嵌套拼接
        List<TestEntity> andEntitys1 = mongoTemplate.find(new Query().addCriteria(
                new Criteria()
                        .where("name")
                        .regex("张")
                        .and("age")
                        .gte(15)
        ), TestEntity.class);
        System.out.println(andEntitys1);

        /**
         * or
         * 只能使用 Criteria.orOperator()
         */
        //查询所有成年的或者小于三岁的
        //select * from user where user.age > 18 or user.age < 3
        List<TestEntity> test_entity = mongoTemplate.find(new Query(
                new Criteria()
                        .orOperator(
                                Criteria.where("age")
                                        .lt(3),
                                Criteria.where("age")
                                        .gt(18)
                        )
        ), TestEntity.class, "test_entity");
        System.out.println("test_entity" + test_entity);
4-使用原生语句查询
        //查询年龄小于16大于14的
        //原生语句:db.test_entity.find({'$and':[{'age':{'$gt':14}},{'age':{'$lt':16}}]})
        //使用原生语句,可使用属性名和字段名(包括混合使用)
        String bson = "{'$and':[{'age':{'$gt':2}},{'age':{'$lt':19}}],'username':/李/}";
        BasicQuery basicQuery = new BasicQuery(bson);
        basicQuery.with(Sort.by(Sort.Order.asc(getFieldName(TestEntity::getAge))))
                .fields()
                //包含指定字段,可使用属性名和字段名(包括混合使用),排除和包含同时只能用一个
                .include("name");
                //排除指定字段,可使用属性名和字段名(包括混合使用)
//                .exclude("username");
        List<TestEntity> testEntities = mongoTemplate.find(basicQuery, TestEntity.class);
        System.out.println(testEntities);
5-统计查询
        /**
         * count 总数查询
         * 传入 查询对象,实体类.class 或者 查询对象,查询的集合名称
         * 传入 [Query,Entity.class] / [Query,collectionName]
         */
        long count = mongoTemplate.count(
                new Query(
                        Criteria.where("age")
                                .gt(14)  
                ),
                "test_entity");
        System.out.println("count->>" + count);
3-新增修改(save)
1-新增
//id存在时为更新,否则则插入,返回更新或插入后的实体
//根据返回实体判断是否插入成功
TestEntity save = mongoTemplate.save(new TestEntity());
2-修改
    /**
     * 更新/修改
     * 修改使用的是 {'$set':{'age':12,'name':'张三'}}
     * 只会修改指定列,其他列数据不会被默认置空或修改
     */
    @Test
    public void updateDocument(){
        /**
         * 传入 修改条件,修改后的值,修改的集合的.class
         * upsert()         更新,若无匹配条件则插入
         * updateMulti()    更新匹配到的所有,可以传入 BasicUpdate 对象,实现类似原生语句更新
         * updateFirst()    更新匹配到的第一个,可以传入 BasicUpdate 对象,实现类似原生语句更新
         */

        /**
         * upsert
         */
        //构建 update 对象,并设置修改的 key 和对应的 value
        Update update = new Update();
        //使用 setOnInsert() 若修改无匹配数据则插入 setOnInsert() 中指定的值,update 指定的修改项也会被插入
        //使用 mongotemplate.updsert 时, update.set指定的值一定会被修改/或插入,update.setOnInsert指定的值只会在没有条件匹配时进行插入
        update.setOnInsert("id",6);
        update.set(getFieldName(TestEntity::getAge),28);
        //构建 query 对象,设置需要匹配修改的条件
        Query query = new Query().addCriteria(Criteria.where(getFieldName(TestEntity::getName)).is("张三"));
        //执行修改,并拿到修改的结果集
        //匹配条件修改,若无匹配则执行插入
        UpdateResult upsert = mongoTemplate.upsert(query, update, TestEntity.class);
        System.out.println(upsert);
        //修改匹配条件的第一条
        UpdateResult updateFirstResult = mongoTemplate.updateFirst(query, update, TestEntity.class);
        //修改匹配条件的所有数据
        UpdateResult updateAllResult = mongoTemplate.updateMulti(query, update, TestEntity.class);
        /**
         * UpdateResult 解析
         * getUpsertedId() 获取插入的编号
         * getMatchedCount() 获取匹配的行数
         * getModifiedCount() 获取修改的行数
         */
        System.out.println("新增时id->>"+updateAllResult.getUpsertedId());
        System.out.println("条件匹配行数->>"+updateAllResult.getMatchedCount());
        System.out.println("实际修改行数->>"+updateAllResult.getModifiedCount());
    }
3-使用原生语句修改
        /**
         * 使用自定义的 bson 进行修改指定字段时需使用 '$set' 进行修改指定
         * 不使用 $set : 修改指定字段,其余除id字段全部置空
         * 使用 $set : 仅修改指定字段,其余字段不做任何操作
         */
        String bson = "{'$set':{'age':20,'isDeleted':false}}";
        BasicUpdate basicUpdate = new BasicUpdate(bson);
        UpdateResult updateResult = mongoTemplate.updateFirst(
                //筛选条件
                new Query().addCriteria(
                        Criteria.where(
                                getFieldName(TestEntity::getName)
                        ).is("张三")
                ),
                //修改后的值
                basicUpdate,
                //实体类的class或集合名称
                TestEntity.class
        );
        System.out.println("新增时id->>"+updateResult.getUpsertedId());
        System.out.println("条件匹配行数->>"+updateResult.getMatchedCount());
        System.out.println("实际修改行数->>"+updateResult.getModifiedCount());
4-删除
1-删除所有文档
        //使用实体类的 @Document 注解中的值作为集合名删除
        DeleteResult removeA = mongoTemplate.remove(new Query(), TestEntity.class);
        //直接传入集合名和删除条件
        DeleteResult removeB = mongoTemplate.remove(new Query(), "test_entity");
        //通过删除结果集获取删除的行数
        long deletedCount = removeA.getDeletedCount();
        //当删除所有文档时,不如直接删除整个集合效率高
        mongoTemplate.dropCollection(TestEntity.class);
        mongoTemplate.dropCollection("collectionName/删除的集合名称");
2-删除指定文档
        //删除指定文档,传入删除条件和[集合名称/实体类的.class]
        DeleteResult test_entity = mongoTemplate.remove(new Query(), "test_entity");
        //打印删除结果集
        System.out.println("删除结果集->>"+test_entity);
        //获取删除的行数
        System.out.println("受影响的行数->>"+test_entity.getDeletedCount());
5-聚合管道使用
1-基础聚合
        /**
         * 获取年龄大于18小于100的并按照年龄倒叙排列
         * 然后获取以2条每页分页获取第二页的数据
         */
        //过滤掉年龄小于10大于100的
        Criteria age = Criteria.where("age")
                .gt(18)
                .lt(100);
        MatchOperation match = Aggregation.match(age);
        //年龄倒序排列
        SortOperation sort = Aggregation.sort(Sort.Direction.DESC, "age");
        //分页
        SkipOperation skip = Aggregation.skip(2);
        LimitOperation limit = Aggregation.limit(2);
        //多聚合对象构建
        TypedAggregation<TestEntity> testEntityTypedAggregation = TypedAggregation.newAggregation(TestEntity.class, match, sort, skip, limit);
        //聚合查询
        AggregationResults<TestEntity> aggregate = mongoTemplate.aggregate(testEntityTypedAggregation, TestEntity.class);
        //获取原始数据
        Document rawResults = aggregate.getRawResults();
        //获取对象映射后的数据
        List<TestEntity> mappedResults = aggregate.getMappedResults();
        System.out.println(mappedResults);
2-多表关联
        /**
         * 获取年龄大于18小于100的并按照年龄倒叙排列
         * 然后获取以2条每页分页获取第二页的数据
         */
        //过滤掉年龄小于10大于100的
        Criteria age = Criteria.where("age")
                .gt(18)
                .lt(100);
        MatchOperation match = Aggregation.match(age);
        //年龄倒序排列
        SortOperation sort = Aggregation.sort(Sort.Direction.DESC, "age");
        //分页
        SkipOperation skip = Aggregation.skip(2);
        LimitOperation limit = Aggregation.limit(2);
        //多聚合对象构建
        TypedAggregation<TestEntity> testEntityTypedAggregation = TypedAggregation.newAggregation(TestEntity.class, match, sort, skip, limit);
        //聚合查询
        AggregationResults<TestEntity> aggregate = mongoTemplate.aggregate(testEntityTypedAggregation, TestEntity.class);
        //获取原始数据
        Document rawResults = aggregate.getRawResults();
        //获取对象映射后的数据
        List<TestEntity> mappedResults = aggregate.getMappedResults();
        System.out.println(mappedResults);

工具类

分页工具类(PageSize,PageNum和Limit,Skip转换)

import com.gitee.xiezengcheng.fluent.mongo.reflection.ReflectionUtil;
import com.gitee.xiezengcheng.fluent.mongo.reflection.SerializableFunction;
import org.springframework.data.mongodb.core.query.Query;

/**
 * @author ChengQiuBo
 * @version 1.0
 * @date 2022/11/15 17:21
 * @description 分页工具
 */
// 需导入依赖包
//            <!--        mongodb 条件构建器-->
//        <dependency>
//            <groupId>com.gitee.xiezengcheng</groupId>
//            <artifactId>fluent-mongo</artifactId>
//            <version>1.1.1</version>
//            <!--            <exclusions>-->
//            <!--                <exclusion>-->
//            <!--                    <groupId>org.springframwork.boot</groupId>-->
//            <!--                    <artifactId>spring-boot-starter-data-mongodb</artifactId>-->
//            <!--                </exclusion>-->
//            <!--            </exclusions>-->
//        </dependency>

@SuppressWarnings("all")
public class PageUtil {

    private Integer pageSize = 10;
    private Integer pageNumber = 1;

    private PageUtil(Integer pageSize, Integer pageNumber) {
        if (pageSize <= 0){
            pageSize = 10;
        }else {
            this.pageSize = pageSize;

        }
        if (pageNumber < 1){
            pageNumber = 1;
        }else {
            this.pageNumber = pageNumber;
        }
    }

    private PageUtil() {}

    /**
     * 工厂模式生产 PageUtil 对象
     */
    public static PageUtil build(){
        return new PageUtil();
    }

    public static PageUtil build(Integer pageSize, Integer pageNumber){
        return new PageUtil(pageSize,pageNumber);
    }

    /**
     * 通过传入的 pageSize 和 pageNumber 获取 limit(查询条数)
     */
    public Integer getLimit() {
        return pageSize;
    }

    /**
     * 通过传入的 pageSize 和 pageNumber 获取 skip(跳过条数)
     */
    public Integer getSkip() {
        return (pageNumber - 1) * pageSize;
    }

    /**
     * 设置页面大小
     */
    public PageUtil setPageSize(Integer pageSize) {
        this.pageSize = pageSize;
        return this;
    }

    /**
     * 设置页码
     */
    public PageUtil setPageNumber(Integer pageNumber) {
        this.pageNumber = pageNumber;
        return this;
    }

    /**
     * 构建 Mongo Query 对象
     */
    public static Query buildMongoQuery(Query query, Integer pageSize, Integer pageNumber){
        return query.limit(pageSize)
                .skip((pageNumber - 1) * pageSize);
    }

    public static Query buildMongoQuery(Integer pageSize, Integer pageNumber){
        return new Query().limit(pageSize)
                .skip((pageNumber - 1) * pageSize);
    }

    public Query buildMongoQuery(Query query){
        return query.limit(getLimit())
                .skip(getSkip());
    }

    public Query buildMongoQuery(){
        return new Query().limit(getLimit())
                .skip(getSkip());
    }

    public static<T,R> String getFieldName(SerializableFunction<T, R> fun){
        return ReflectionUtil.getFieldName(fun);
    }
}

管道聚合工具类
import com.gitee.xiezengcheng.fluent.mongo.reflection.SerializableFunction;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.aggregation.*;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
import org.springframework.util.StringUtils;

import java.util.ArrayList;

import static com.gitee.xiezengcheng.fluent.mongo.reflection.ReflectionUtil.getFieldName;
import static com.gitee.xiezengcheng.fluent.mongo.reflection.ReflectionUtil.getField;

/**
 * @author ChengQiuBo
 * @version 1.0
 * @date 2022/11/23 17:22
 * @description 聚合管道工具类
 * 所依赖包如下
 */

/**
 * <dependency>
 * <groupId>com.gitee.xiezengcheng</groupId>
 * <artifactId>fluent-mongo</artifactId>
 * <version>1.1.1</version>
 * </dependency>
 *
 * <dependency>
 * <groupId>org.springframework.boot</groupId>
 * <artifactId>spring-boot-starter-data-mongodb</artifactId>
 * </dependency>
 */

@SuppressWarnings("all")
public class AggregationUtil {


    private AggregationUtil() {
    }

    private ArrayList<AggregationOperation> list = new ArrayList<AggregationOperation>();

    public static AggregationUtil build() {
        return new AggregationUtil();
    }

    /**
     * 获取管道聚合条件集合
     */
    public ArrayList<AggregationOperation> getOperations(){
        return this.list;
    }

    /**
     * 获取最终 TypeAggregation 对象
     */
    public <T> TypedAggregation<T> buildTypedAggregation(Class<T> type) {
        TypedAggregation<T> tTypedAggregation = TypedAggregation.newAggregation(type, list);
        return tTypedAggregation;
    }


    /**
     * 构建投影管道($project)
     * 若实体类属性注解名与属性名不一致则结果集无法进行映射该属性
     * 传入时需要传入属性名,而不是注解值(即mongodb集合字段名)
     * 官方同样存在该问题
     */
    public AggregationUtil project(String... strings) {
        if (strings != null && strings.length > 0) {
            ProjectionOperation project = Aggregation.project(strings);
            list.add(project);
        }
        return this;
    }

    public <T, R> AggregationUtil project(SerializableFunction<T, R>... funs) {
        if (funs != null && funs.length > 0) {
            ArrayList<String> stringList = new ArrayList<>();
            for (SerializableFunction<T, R> fun : funs) {
                //获取@Field注解的值,没有使用注解直接获取属性名
//                String fieldName = getFieldName(fun);
                //只获取属性名
                String fieldName = getField(fun).getName();
                System.out.println(fieldName);
                stringList.add(fieldName);
            }
            String[] objects = new String[funs.length];
            for (int i = 0; i < stringList.size(); i++) {
                objects[i] = stringList.get(i);
            }
            ProjectionOperation project = Aggregation.project(objects);
            list.add(project);
        }
        return this;
    }

    /**
     * 构建过滤管道($match)
     */
    public AggregationUtil match(CriteriaDefinition... criteriaDefinition) {
        if (criteriaDefinition != null && criteriaDefinition.length > 0) {
            for (CriteriaDefinition definition : criteriaDefinition) {
                list.add(Aggregation.match(definition));
            }
        }
        return this;
    }

    /**
     * 构建计数管道($count)
     */
    public AggregationUtil count(String fieldName) {
        CountOperation.CountOperationBuilder count = Aggregation.count();
        boolean empty = StringUtils.isEmpty(fieldName);
        if (empty) {
            fieldName = "countField";
        }
        CountOperation as = count.as(fieldName);
        list.add(as);
        return this;
    }

    public <T, R> AggregationUtil count(SerializableFunction<T, R> fun) {
        return count(getFieldName(fun));
    }

    /**
     * 分组($group)
     */
    public AggregationUtil group(String... fields) {
        if (fields != null && fields.length > 0) {
            GroupOperation group = Aggregation.group(fields);
            list.add(group);
        }
        return this;
    }

    public <T, R> AggregationUtil group(SerializableFunction<T, R>... fields) {
        if (fields != null && fields.length > 0) {
            ArrayList<String> stringList = new ArrayList<>();
            for (SerializableFunction<T, R> fun : fields) {
                String fieldName = getFieldName(fun);
                stringList.add(fieldName);
            }
            String[] objects = (String[]) stringList.toArray();
            GroupOperation group = Aggregation.group(objects);
            list.add(group);
        }
        return this;
    }

    /**
     * 拆分($unwind)
     * 指定字段为 null 时,默认不输出文档
     */
    public AggregationUtil unwind(String... fields) {
        unwind(false, fields);
        return this;
    }

    public <T, R> AggregationUtil unwind(SerializableFunction<T, R>... fields) {
        unwind(false, fields);
        return this;
    }

    public AggregationUtil unwind(boolean preserveNullAndEmptyArrays, String... fields) {
        if (fields != null && fields.length > 0) {
            for (String field : fields) {
                list.add(Aggregation.unwind(field, preserveNullAndEmptyArrays));
            }
        }
        return this;
    }

    public <T, R> AggregationUtil unwind(boolean preserveNullAndEmptyArrays, SerializableFunction<T, R>... fields) {
        if (fields != null && fields.length > 0) {
            for (SerializableFunction<T, R> field : fields) {
                list.add(Aggregation.unwind(getFieldName(field), preserveNullAndEmptyArrays));
            }
        }
        return this;
    }

    /**
     * 截取($limit)
     * 若输入值小于 0 ,则会使用默认截取 10
     */

    public AggregationUtil limit(long maxElements) {
        if (maxElements < 0) {
            maxElements = 10;
        }
        LimitOperation limit = Aggregation.limit(maxElements);
        list.add(limit);
        return this;
    }

    /**
     * 跳过($skip)
     */
    public AggregationUtil skip(long elementsToSkip) {
        if (elementsToSkip < 0) {
            elementsToSkip = 0;
        }
        SkipOperation skip = Aggregation.skip(elementsToSkip);
        list.add(skip);
        return this;
    }

    /**
     * 排序($sort)
     */
    public AggregationUtil sort(Sort sort) {
        SortOperation sortOperation = Aggregation.sort(sort);
        list.add(sortOperation);
        return this;
    }

    /**
     * 表关联
     */
    public AggregationUtil lookup(String from, String localField, String foreignField, String as) {
        LookupOperation lookup = Aggregation.lookup(from, localField, foreignField, as);
        list.add(lookup);
        return this;
    }

    public <T, R> AggregationUtil lookup(Class fromClass, SerializableFunction<T, R> localField, SerializableFunction<T, R> foreignField, String as) throws Exception {
        return lookup(getDocumentAnnoValue(fromClass), getFieldName(localField), getFieldName(foreignField), as);
    }


    /**
     * 反射获取类上 @Document 注解值
     */
    public static String getDocumentAnnoValue(Class zClass) throws Exception {
        boolean annotationPresent = zClass.isAnnotationPresent(Document.class);
        if (annotationPresent) {
            Document annotation = (Document) zClass.getAnnotation(Document.class);
            String collection = annotation.collection();
            String value = annotation.value();
            String collectionName = "";
            if (!isEmpty(collection) && !isEmpty(value)) {
                if (collection.equals(value)) {
                    collectionName = value;
                }
            } else if (isEmpty(collection) && !isEmpty(value)) {
                collectionName = value;
            } else if (!isEmpty(collection) && isEmpty(value)) {
                collectionName = collection;
            }
            return collectionName;
        }
        throw new Exception("类 \"" + zClass.getName() + "\" 没有使用 MongoDB @Document 注解标识集合名称,无法获取集合名");
    }

    private static boolean isEmpty(String str) {
        return str == null || "".equals(str);
    }

//    public static void main(String[] args) throws Exception {
//        System.out.println(getDocumentAnnoValue(TestEntity.class));
//    }


}

;