项目中使用mongodb也有5个多月了,总结一下MongoTemplate的聚合查询

以项目中的一个走访表作为例子

在对应项目中引入对应版本的spring-boot-data-mongodb依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>

然后yml配置文件中配置好mongodb的连接信息就可以了

spring:
  data:
    mongodb:
      uri: mongodb://账号:密码@ip:port/库名 
logging:
  level:
    org.springframework.data.mongodb.core: debug

 monggodb集合对应的实体类:

package com.dcboot.module.visit.mongodb.entity;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

import java.util.List;

/**
 * @author xiaomifeng1010
 * @version 1.0
 * @date: 2022/7/16 15:47
 * @Description
 */
@Document("visitObjectDeatil")
@ApiModel(value = "走访对象详情信息")
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class VisitObjectDeatil {

    private Long taskObjectId;

    @Id
    private Long id;

    /**
     * 走访状态(只有走访中和已走访两种状态了)
     */
    private Integer visitStatus;

    /**
     * 属地镇街(顺德区的10个镇街)
     * {@link com.dcboot.module.common.enums.task.ShunDeDistrictEnum}
     */
    @ApiModelProperty(name = "townOrStreet",value = "属地镇街(顺德区的10个镇街)")
    private String townOrStreet;
    /**
     * 走访主题
     * {@link com.dcboot.module.common.enums.task.TaskSubjectEnum}
     */
    @ApiModelProperty(name = "visitSubject",value = "走访主题")
    private Integer visitSubject;


    /**
     * 走访对象
     */
    @ApiModelProperty(name = "visitObject",value = "走访对象(6种类别)")
    private VisitObject visitObject;


    /**
     * 走访详情说明
     */
    @ApiModelProperty(name = "visitDeatilDescription",value = "走访详情说明")
    private String visitDeatilDescription;

    /**
     * 走访附件信息列表
     */
    @ApiModelProperty(name = "attachmentList",value = "走访附件")
    private List<Attachment> attachmentList;

    /**
     * 走访现场图片
     */
    @ApiModelProperty(name = "visitPictureList",value = "走访现场图片")
    private List<Attachment> visitPictureList;


    /**
     * 企业联系人名片
     */
    @ApiModelProperty(name = "enterpriseContactPersonBusinessCardList",value = "企业联系人名片")
    private List<EnterpriseContactPersonBusinessCard> enterpriseContactPersonBusinessCardList;

//    private List<VisitThePersonOfEnterprise> visitThePersonOfEnterpriseList;

    @ApiModelProperty(name = "visitEnterpriseBaseInfo",value = "走访企业基本信息")
    private VisitEnterpriseBaseInfo visitEnterpriseBaseInfo;

    /**
     * 财务指标
     */
    @ApiModelProperty(name = "financeIndexList",value = "财务指标")
    private List<FinanceIndex> financeIndexList;

    /**
     * 企业规模
     */
    @ApiModelProperty(name = "enterpriseScale",value = "企业规模")
    private EnterpriseScale enterpriseScale;

    /**
     * 企业营销情况
     */
    @ApiModelProperty(name = "enterpriseMarktingCondition",value = "企业营销情况")
    private EnterpriseMarktingCondition enterpriseMarktingCondition;
    /**
     * 经营主要指标
     */
    @ApiModelProperty(name = "mainOperatingIndicators",value = "经营主要指标")
    private MainOperatingIndicators mainOperatingIndicators;

    /**
     * 企业融资情况和意愿
     */
    @ApiModelProperty(name = "enterpriseFinancingAndWilling",value = "企业融资意愿")
    private EnterpriseFinancingAndWilling enterpriseFinancingAndWilling;

    /**
     * 企业专利著作情况
     */
    @ApiModelProperty(name = "enterprisePatentAndBook",value = "企业专利著作情况")
    private EnterprisePatentAndBook enterprisePatentAndBook;

    /**
     * 企业陪同走访人
     */
    @ApiModelProperty(name = "companionPerson",value = "企业陪同走访人")
    private String companionPerson;
    /**
     * 企业标签
     */
    @ApiModelProperty(name = "enterpriseLabel",value = "企业标签")
    private String enterpriseLabel;


    /**
     * 走访形式
     */
    @ApiModelProperty(name = "visitMode",value = "走访形式")
    private Integer visitMode;

    /**
     * 是否自主走访
     */
    private Integer isSelfVisit;

    /**
     * 走访对象类型 分为:企业主体和非企业主体 1 企业主体 0 非企业主体
     */
    @ApiModelProperty(name = "visitObjectType",value = "走访对象类型 分为:企业主体和非企业主体 1 企业主体 0 非企业主体")
    private Integer visitObjectType;

    /**
     * 走访人
     */
    @ApiModelProperty(name = "visitPersonId",value = "走访人id")
    private Long visitPersonId;

    private String visitPerson;

    /**
     * 走访人所在科室
     */
    @ApiModelProperty(name = "visitDepartmentId",value = "走访人所在科室id")
    private Long visitDepartmentId;

    private String visitDepartment;


    @ApiModelProperty(name = "visitTime",value = "走访日期")
    private String visitTime;

   private String createTime;

   private String updateTime;

   private String createBy;

   private String updateBy;

}

内嵌对象

package com.dcboot.module.visit.mongodb.entity;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * @author xiaomifeng1010
 * @version 1.0
 * @date: 2022/7/18 11:11
 * @Description
 */
@Data
@ApiModel(value = "走访对象")
@JsonIgnoreProperties(ignoreUnknown = true)
public class VisitObject {


    private String visitObjectId;

    /**
     * 走访对象类型
     * {@link com.dcboot.module.common.enums.task.VisitObjectTypeEnum}
     */
    @ApiModelProperty(name = "type",value = "走访对象类型")
    private Integer type;

    @ApiModelProperty(name = "typeName",value = "走访对象类型名称")
    private String typeName;

    @ApiModelProperty(name = "visitObjectName",value = "走访对象名称")
    private String visitObjectName;
}

内部数组对象:

package com.dcboot.module.visit.mongodb.entity;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.math.BigDecimal;

/**
 * @author xiaomifeng1010
 * @version 1.0
 * @date: 2022/7/16 18:47
 * @Description 财务指标
 */
@Data
@ApiModel(value = "财务指标")
@JsonIgnoreProperties(ignoreUnknown = true)
public class FinanceIndex {

    /**
     * 年份
     */
    private String year;
    /**
     * 营业总收入
     */
    @ApiModelProperty(name = "income", value = "营业总收入")
    private BigDecimal income;
    /**
     * 利润总额
     */
    @ApiModelProperty(name = "profit", value = "利润总额")
    private BigDecimal profit;

    /**
     * 纳税总额
     */
    @ApiModelProperty(name = "amountOfTax", value = "纳税总额")
    private BigDecimal amountOfTax;

    /**
     * 资产总额
     */
    @ApiModelProperty(name = "totalAmountOfAssets", value = "资产总额")
    private BigDecimal totalAmountOfAssets;
    /**
     * 负债总额
     */
    @ApiModelProperty(name = "totalAmountOfLiabilities", value = "负债总额")
    private BigDecimal totalAmountOfLiabilities;

    /**
     * 流动比率
     */
    @ApiModelProperty(name = "currentRatio", value = "流动比率")
    private BigDecimal currentRatio;
    /**
     * 速动比率
     */
    @ApiModelProperty(name = "quickRatio", value = "速动比率")
    private BigDecimal quickRatio;
    /**
     * 资产负债率
     */
    @ApiModelProperty(name = "assetsLiabilityRatio", value = "资产负债率")
    private Double assetsLiabilityRatio;

    /**
     * 净资产收益率
     */
    @ApiModelProperty(name = "netAssetsYieldsRatio",value = "净资产收益率")
    private BigDecimal netAssetsYieldsRatio;

    /**
     * 资产回报率
     */
    @ApiModelProperty(name = "assetsReturnRatio",value = "资产回报率")
    private BigDecimal assetsReturnRatio;

    /**
     * 毛利率
     */
    @ApiModelProperty(name = "profitRatio",value = "毛利率")
    private BigDecimal profitRatio;

    /**
     * 企业所得税实际纳税额
     */
    @ApiModelProperty(name = "actualTaxAmountOfEnterpriseIncomeTax",value = "企业所得税实际纳税额")
    private BigDecimal actualTaxAmountOfEnterpriseIncomeTax;

    /**
     * 经营活动产生的现金流量净额
     */
    @ApiModelProperty(name = "netCashFlowFromOperatingActivities",value = "经营活动产生的现金流量净额")
    private BigDecimal netCashFlowFromOperatingActivities;
    /**
     * 研发投入
     */
    @ApiModelProperty(name = "researchInvestment",value = "研发投入")
    private BigDecimal researchInvestment;

    /**
     * 文件列表
     */
//   private List<Attachment> attachmentList;

}

查询例子1

package com.dcboot.module.visit.task.service.impl;

import com.dcboot.module.visit.mongodb.entity.VisitObjectDeatil;
import com.dcboot.module.visit.task.common.PageListResponse;
import com.dcboot.module.visit.task.service.PortalVisitService;
import com.dcboot.module.visit.task.vo.SimpleVisitRecordVO;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;
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.aggregation.TypedAggregation;
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;

/**
 * @author xiaomifeng1010
 * @version 1.0
 * @date: 2022/7/15 11:46
 * @Description
 */
@Service
@Setter(onMethod_=@Autowired)
public class PortalVisitServiceImpl implements PortalVisitService {

    private MongoTemplate mongoTemplate;

    @Override
    public PageListResponse<SimpleVisitRecordVO> getVisitRecords(Long page,Integer size,String enterpriseName) {
        Criteria criteria = Criteria.where("visitObject.visitObjectName").is(enterpriseName);
        Query query = Query.query(criteria);
        TypedAggregation<VisitObjectDeatil> visitObjectDeatilTypedAggregation = Aggregation.newAggregation(
                VisitObjectDeatil.class,
//                 相当于select
                Aggregation.project()
                        .and("visitPerson").as("userName")
                        .and("visitObject.visitObjectName").as("enterpriseName")
                        .and("visitTime").as("visitTime").andExclude("_id")
                .and("createTime").as("createTime"),
//                查询条件,相当于where
                Aggregation.match(Criteria.where("enterpriseName").is(enterpriseName)),
                Aggregation.sort(Sort.Direction.DESC, "createTime"),
                Aggregation.skip((page - 1) * size),
                Aggregation.limit(size)
        );
        AggregationResults<SimpleVisitRecordVO> mapAggregationResults = mongoTemplate.aggregate(visitObjectDeatilTypedAggregation, SimpleVisitRecordVO.class);
        List<SimpleVisitRecordVO> mappedResults = mapAggregationResults.getMappedResults();
        mappedResults.forEach(System.out ::println);
        long totalCount = mongoTemplate.count(query, VisitObjectDeatil.class);
        PageListResponse<SimpleVisitRecordVO> pageListResponse = new PageListResponse<>();
        pageListResponse.setRecordList(mappedResults);
        pageListResponse.setTotalCount(totalCount);
        return pageListResponse;
    }
}

,这是一个分页查询,需要分页查询出结果,并且还要查询出总条数,提供给前端,所以只用一个聚合管道查询不够,聚合管道查询出分页的数据,然后还有一个查询查满足查询条件的总条数,然后封装返回给前端

mongodb聚合查询分页查出来的数据,对应的VO实体类:

package com.dcboot.module.visit.task.vo;

import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author xiaomifeng1010
 * @version 1.0
 * @date: 2022/7/19 20:18
 * @Description
 */
@NoArgsConstructor
@Data
public class SimpleVisitRecordVO {

    private String visitTime;
    private String userName;
    private String enterpriseName;
}

还有一个mongodb集合collection是同事创建的,他创建的实体类是这样的:

package com.dcboot.module.manage.vo.req;

import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

/**
 * @description: ScienceProjectParams
 * @date: 2022/4/25 16:32
 * @author: HCF
 * @version: 1.0
 */
@Document(collection = "scienceProjectParams")
@ApiModel(value = "科技项目参数")
@Data
@Accessors(chain = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ScienceProjectParams implements Serializable {

    @ApiModelProperty(value = "_id")
    private String _id;

    @ApiModelProperty(value = "数据")
    private Object data;

    @ApiModelProperty(value = "类别")
    private String category;

    @ApiModelProperty(value = "备注")
    private List<Object> remarks;

    @ApiModelProperty(value = "创建时间")
    private String createTime;

    @ApiModelProperty(value = "修改时间")
    private String updateTime;
}

那么在使用聚合管道查询时:

int thisYear = DateUtil.thisYear();
            String thisYearStr = String.valueOf(thisYear);
            Aggregation aggregation  = Aggregation.newAggregation(
                    Aggregation.project().and("data.QYMC").as("enterpriseName")
                            .and("data.TYSHXYDM").as("uscc")
                            .andExclude("_id")
                            .and("createTime").as("createTimeStr")
                            .and("createTime").substring(5,2).as("month")
                            .and("createTime").substring(0,4).as("year")
                            .and("data.type").as("typeCode"),
                    Aggregation.match(Criteria.where("typeCode").is(typeCode).and("year").is(thisYearStr)),
                    Aggregation.sort(Sort.Direction.DESC, "createTimeStr")

            );
            AggregationResults<EnterpriseDetailForTypeBO> aggregate = mongoTemplate.aggregate(aggregation,mongoTemplate.getCollectionName(ScienceProjectParams.class),EnterpriseDetailForTypeBO.class);
            List<EnterpriseDetailForTypeBO> mappedResults = aggregate.getMappedResults();

说明,由于 ScienceProjectParams类中有个属性data是Object类型,所以不能使用TypedAggregation<ScienceProjectParams>,需要使用不指定泛型类型的Aggregation 然后在使用mongoTemplate.aggregate方法时,使用 mongoTemplate.getCollectionName(ScienceProjectParams.class),传入集合名;

如果使用TypedAggregation<ScienceProjectParams>这种带泛型类型聚合api,会报异常,提示没有实体类可以转换成Object类(Couldn't find PersistentEntity for type java.lang.Object!)

比如之前的写法和异常出错

mongodb 分组聚合 Aggregation java mongotemplate聚合_mongodb

 因为属性data是Object类型 

出现异常提示

mongodb 分组聚合 Aggregation java mongotemplate聚合_mongotemplate_02

所以改成上边的写法就可以正常聚合查询了;

而上一个查询例子中,我自己创建的一个mongodb的collection对应的实体类可以使用TypedAggregation<VisitObjectDeatil>是因为,我创建的实体类VisitObjectDeatil的属性中没有Object类型

查询3

聚合中如果带有分组和求和,则查询出来的字段会只有分组的字段和求和的字段,其他字段是查询不出来的,而且查询出来的结构也是分组字段和求和字段是隔离开的

例如:

for (int i = 1; i <= 2 ; i++) {
            String typeCode = EnterpriseTypeEnum.getTypeCode(i);
            Aggregation aggregation  = Aggregation.newAggregation(
                    Aggregation.project().and("data.QYMC").as("enterpriseName")
                            .and("data.TYSHXYDM").as("uscc")
                            .andExclude("_id")
                            .and("createTime").as("createTime")
                            .and("createTime").substring(5,2).as("month")
                            .and("createTime").substring(0,4).as("year")
                            .and("data.type").as("typeCode")
                            .and("enterpriseNum"),
                    Aggregation.match(Criteria.where("typeCode").is(typeCode)),
                    Aggregation.sort(Sort.Direction.DESC, "createTime"),
                    Aggregation.group("month","year","typeCode").count().as("enterpriseNum")

            );
            AggregationResults<Map> aggregate = mongoTemplate.aggregate(aggregation,mongoTemplate.getCollectionName(ScienceProjectParams.class),Map.class);
            List<Map> mappedResults = aggregate.getMappedResults();
            if (CollectionUtils.isNotEmpty(mappedResults)){
                JSONArray jsonArray = JSONUtil.parseArray(mappedResults);
                int size = jsonArray.size();
                for (int j = 0; j < size; j++) {
                    JSONObject jsonObject = jsonArray.getJSONObject(j);
                    Integer enterpriseNum = jsonObject.getInt("enterpriseNum", NumberUtils.INTEGER_ZERO);
                    JSONObject groupInfo = jsonObject.getJSONObject("_id");
                    String month = groupInfo.getStr("month");
                    Integer monthNum=0;
                    if (NumberUtil.isInteger(month)){
                        monthNum = Integer.valueOf(month);
                    }
                    String year = groupInfo.getStr("year");
                    Integer yearNum=0;
                    if (NumberUtil.isInteger(year)) {
                        yearNum = Integer.valueOf(year);
                    }
                    groupInfo.getStr("typeCode");
                    EnterpriseTypeStatistics one = enterpriseTypeStatisticsService.getOne(Wrappers.<EnterpriseTypeStatistics>lambdaQuery()
                            .eq(EnterpriseTypeStatistics::getIsValid, 1)
                            .eq(EnterpriseTypeStatistics::getMonth, monthNum)
                            .eq(EnterpriseTypeStatistics::getType, i)
                            .eq(EnterpriseTypeStatistics::getYear, yearNum));
                    EnterpriseTypeStatistics enterpriseTypeStatistics = new EnterpriseTypeStatistics();
                    enterpriseTypeStatistics.setEnterpriseNum(enterpriseNum);
                    enterpriseTypeStatistics.setYear(yearNum);
                    enterpriseTypeStatistics.setMonth(monthNum);
                    enterpriseTypeStatistics.setTypeCode(typeCode);
                    enterpriseTypeStatistics.setType(i);
                    String typeName = EnterpriseTypeEnum.getTypeName(i);
                    enterpriseTypeStatistics.setIsValid(1);
                    enterpriseTypeStatistics.setTypeName(typeName);
                    if (Objects.isNull(one)){
                        enterpriseTypeStatistics.insert();
                    }else {
                        Long id = one.getId();
                        enterpriseTypeStatistics.setId(id);
                        boolean update = enterpriseTypeStatistics.updateById();
                    }

                }
            }
        }

 聚合查询出来的数据结构是这样的

[{
	"_id": {
		"month": "05",
		"year": "2022",
		"typeCode": "GXJSQYRD"
	},
	"enterpriseNum": 4905
}, {
	"_id": {
		"month": "06",
		"year": "2022",
		"typeCode": "GXJSQYRD"
	},
	"enterpriseNum": 2
}]

如果 Aggregation.group("month","year","typeCode").count().as("enterpriseNum")这个聚合操作,只是分组,没有计数count操作,则可以直接映射给一个实体类的形式

比如这个聚合查询中group操作之后,没有计数或者求和

/**
     *
     * @return
     */
    @GetMapping("/web/thirteenthToTwentyEighthTypeDetailRecordTest")
    @ApiOperation("添加第十三类至第二十八类企业数据明细测试")
    public ApiResult thirteenthToTwentyEighthTypeDetailRecordTest(){
        for (int i = 13; i <= 28; i++) {
            List<EnterpriseTypeDetailRecord> enterpriseTypeDetailRecordList = Lists.newArrayList();
            String typeCode = EnterpriseTypeEnum.getTypeCode(i);
            String typeName = EnterpriseTypeEnum.getTypeName(i);
            List<String> enterpriseTypeChildCodeList = enterpriseTypeDetailRecordService.getEnterpriseTypeChildCode(typeCode);
            Aggregation aggregation = null;
            if (i == 13 || Range.closed(18, 20).contains(i) || Range.closed(25, 28).contains(i)) {
                aggregation = Aggregation.newAggregation(
                        Aggregation.project().and("data.list.SBDW").as("enterpriseName")
                                .and("data.list.TYSHXYDM").as("uscc")
                                .andExclude("_id")
                                .and("createTime").substring(5, 2).as("month")
                                .and("createTime").substring(0, 4).as("year")
                                .and("data.type").as("typeCode"),
                        Aggregation.match(Criteria.where("typeCode").in(enterpriseTypeChildCodeList)),
                        Aggregation.group("month","year","enterpriseName","uscc").last("month")
                        .as("month").last("year").as("year").last("enterpriseName").as("enterpriseName")
                        .last("uscc").as("uscc")

                );
            }
            AggregationResults<EnterpriseDetailForTypeBO> aggregate = mongoTemplate.aggregate(aggregation,mongoTemplate.getCollectionName(ScienceProjectParams.class),EnterpriseDetailForTypeBO.class);
            List<EnterpriseDetailForTypeBO> mappedResults = aggregate.getMappedResults();
            log.info("第{}类表的数据:{}",i, JSONUtil.toJsonPrettyStr(mappedResults));
        }
        return ApiResult.success();
    }

那么最终聚合查询出来的数据的格式就是这样的:

[
    {
        "enterpriseName": "广东伊之密精密机械股份有限公司",
        "uscc": "91440606740846335Y",
        "year": 2022,
        "month": 6
    },
    {
        "enterpriseName": "佛山市顺德区北航先进技术产业基地有限公司",
        "uscc": "914406065778638477",
        "year": 2022,
        "month": 6
    },
    {
        "enterpriseName": "广东顺德西安交通大学研究院",
        "uscc": "124406065666480841",
        "year": 2022,
        "month": 6
    },
    {
        "enterpriseName": "广东精进能源有限公司",
        "uscc": "",
        "year": 2022,
        "month": 6
    },
    {
        "enterpriseName": "美的集团股份有限公司",
        "uscc": "91440606722473344C",
        "year": 2022,
        "month": 6
    },
    {
        "enterpriseName": "广东宏石激光技术股份有限公司",
        "uscc": "91440606698184197Y",
        "year": 2022,
        "month": 6
    },
    {
        "enterpriseName": "广东尚研电子科技股份有限公司",
        "uscc": "91440606577885966R",
        "year": 2022,
        "month": 6
    },
    {
        "enterpriseName": "广东德宁水产科技有限公司",
        "uscc": "91440606MA4UWEB632",
        "year": 2022,
        "month": 6
    },
    {
        "enterpriseName": "广东万锦科技股份有限公司",
        "uscc": "",
        "year": 2022,
        "month": 6
    },
    {
        "enterpriseName": "佛山隆深机器人有限公司",
        "uscc": "91440606077869759B",
        "year": 2022,
        "month": 6
    },
    {
        "enterpriseName": "广东光晟物联股份有限公司",
        "uscc": "91440606059922085K",
        "year": 2022,
        "month": 6
    },
    {
        "enterpriseName": "广东美的制冷设备有限公司",
        "uscc": "9144060672547107X0",
        "year": 2022,
        "month": 6
    },
    {
        "enterpriseName": "北京科技大学顺德研究生院",
        "uscc": "12440606MB2C974763",
        "year": 2022,
        "month": 6
    },
    {
        "enterpriseName": "广东顺德工业设计研究院(广东顺德创新设计研究院)",
        "uscc": "124406063980588428",
        "year": 2022,
        "month": 6
    },
    {
        "enterpriseName": "国药集团广东环球制药有限公司",
        "uscc": "914406066176288779",
        "year": 2022,
        "month": 6
    },
    {
        "enterpriseName": "广东博智林机器人有限公司",
        "uscc": "91440606MA520YMC94",
        "year": 2022,
        "month": 6
    },
    {
        "enterpriseName": "广东惠而浦家电制品有限公司",
        "uscc": "91440606617655699P",
        "year": 2022,
        "month": 6
    },
    {
        "enterpriseName": "美的智慧家居科技有限公司",
        "uscc": "91440300342921205X",
        "year": 2022,
        "month": 6
    },
    {
        "enterpriseName": "广东省皓智科技有限公司",
        "uscc": "91440606MA533MMU0G",
        "year": 2022,
        "month": 6
    },
    {
        "enterpriseName": "广东丁沃生医疗器械有限公司",
        "uscc": "91440606MA51758K3X",
        "year": 2022,
        "month": 6
    },
    {
        "enterpriseName": "佛山市顺德区美的电热电器制造有限公司",
        "uscc": "91440606784896596B",
        "year": 2022,
        "month": 6
    },
    {
        "enterpriseName": "佛山市顺德区德雅先进技术融合创新研究院",
        "uscc": "52440606MJL6641547",
        "year": 2022,
        "month": 6
    },
    {
        "enterpriseName": "华南智能机器人创新研究院",
        "uscc": "12440606351932176Q",
        "year": 2022,
        "month": 6
    },
    {
        "enterpriseName": "佛山市顺德区金泰德胜电机有限公司",
        "uscc": "91440606737571924H",
        "year": 2022,
        "month": 6
    },
    {
        "enterpriseName": "广东艾时代生物科技有限责任公司",
        "uscc": "91440606323308667W",
        "year": 2022,
        "month": 6
    },
    {
        "enterpriseName": "广东银珠医药科技有限公司",
        "uscc": "91440400MA4W3Q8NXA",
        "year": 2022,
        "month": 6
    },
    {
        "enterpriseName": "广东永爱医养产业有限公司",
        "uscc": "914406060537673446",
        "year": 2022,
        "month": 6
    },
    {
        "enterpriseName": "广东科龙模具有限公司",
        "uscc": "914406066174753729",
        "year": 2022,
        "month": 6
    },
    {
        "enterpriseName": "佛山市顺德区美的洗涤电器制造有限公司",
        "uscc": "914406067211121414",
        "year": 2022,
        "month": 6
    },
    {
        "enterpriseName": "广东锻压机床厂有限公司",
        "uscc": "91440606193864031B",
        "year": 2022,
        "month": 6
    },
    {
        "enterpriseName": "佛山市顺德区凯硕精密模具自动化科技有限公司",
        "uscc": "91440606675234577Q",
        "year": 2022,
        "month": 6
    },
    {
        "enterpriseName": "广东奥基德信机电有限公司",
        "uscc": "91440606084537584M",
        "year": 2022,
        "month": 6
    },
    {
        "enterpriseName": "广东顺德顺爷水产有限公司",
        "uscc": "91440606MA4W7ELR4U",
        "year": 2022,
        "month": 6
    }
]

就可以直接用定义好的实体类EnterpriseDetailForTypeBO来映射接收数据

package com.dcboot.module.manage.bo;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * @author xiaomifeng1010
 * @version 1.0
 * @date: 2022/8/4 14:43
 * @Description
 */
@Data
public class EnterpriseDetailForTypeBO implements Serializable {

    private static final long serialVersionUID = 4376512901512325623L;
    private String enterpriseName;
    
    /**
    * 统一社会信用代码
    */
    private String uscc;

    private String createTimeStr;

    private Integer year;

    private Integer month;

    private Integer enterpriseNum;

    private String typeCode;
}

查询4

/**
     * @param year
     * @param month
     * @description: 获取某年某月走访企业数量
     * @author: xiaomifeng1010
     * @date: 2022/9/3
     * @return: Integer
     **/
    @Override
    public Integer getVisitConditionStatistics(Integer year, Integer month) {
        String yearStr = StringUtils.EMPTY;
        String monthStr = StringUtils.EMPTY;
        if (Objects.nonNull(year) && Objects.isNull(month)) {
            yearStr = Convert.toStr(year);
        } else if (ObjectUtils.allNotNull(year, month)) {
            yearStr = Convert.toStr(year);
            monthStr = String.format("%02d", month);
        }
        Criteria criteria = new Criteria();
        MatchOperation match = null;
        GroupOperation group = null;
        if (StringUtils.isNotEmpty(yearStr) && StringUtils.isEmpty(monthStr)) {
            match = Aggregation.match(Criteria.where("year").is(yearStr));
            group = Aggregation.group("year", "enterpriseName").last("year").as("year")
                    .last("enterpriseName").as("enterpriseName");
        } else if (!StringUtils.isAnyBlank(monthStr, yearStr)) {
            match = Aggregation.match(Criteria.where("year").is(yearStr).and("month").is(monthStr));
            group = Aggregation.group("year", "month", "enterpriseName").last("year").as("year")
                    .last("month").as("month")
                    .last("enterpriseName").as("enterpriseName");
        } else {
            match = Aggregation.match(criteria);
            group = Aggregation.group("enterpriseName")
                    .last("enterpriseName").as("enterpriseName");
        }
        TypedAggregation<VisitObjectDeatil> visitObjectDeatilTypedAggregation = Aggregation.newAggregation(
                VisitObjectDeatil.class,
                Aggregation.project().and("visitTime").substring(0, 4).as("year")
                        .and("visitTime").substring(5, 2).as("month")
                        .and("visitObject.visitObjectName").as("enterpriseName")
                        .andExclude("_id"),
//              只获取指定年份的数据
                match,
//                使用group去重
                group,
//                统计今年已走访的不重复的企业数量(未走访的不统计,即visitTime为空的记录)
                Aggregation.count().as("count")

        );
        AggregationResults<Map> aggregateResults = mongoTemplate.aggregate(visitObjectDeatilTypedAggregation, Map.class);
        List<Map> mappedResults = aggregateResults.getMappedResults();
        Integer yearOrMonthCount = 0;
        if (CollectionUtils.isNotEmpty(mappedResults)) {
            yearOrMonthCount = MapUtils.getInteger(mappedResults.get(0), "count");
        }
        return yearOrMonthCount;
    }

各类的Aggregation对象是可以抽取出来的,因为有时候,查询条件是变化的,所以需要根据查询条件是否有值,来进行判断是否需要进行某种类型的聚合操作,因为mongodb的聚合管道查询类似于linux中的管道符操作,前一步的操作结果会直接影响下一步的聚合操作,所以聚合查询时,project,match,goup,count等的顺序很重要,比如你在Aggregation.newAggregation()中最先传入了project类型的聚合并给对应的字段取了别名也就是as后的名称,那么你在第二步match的时候,字段就需要使用project操作后的别名,如果还是使用mongodb中collection中的原始的字段名就会报错,就会提示无法找到reference_name,所以这就是管道操作需要注意的地方,第一步操作的结果是下一步操作时的入参参数值,所以一般情况下,需要根据你的查询需求,project,match,goup,sort,sikp,count,unwind,bucket等操作,放在Aggregation.newAggregation()中的顺序都会不一样的,对于mongodb中内置的一些字符串函数和日期函数,主要都是在project类型的聚合操作中进行的,比如上边的例子,为了取出日期中的年份,使用substring函数操作提取日期字符串的前四位,当然如果你保存日期的时候,不是使用String类型,而且DateTime类型,则可以直接使用year,month这类的函数提取,更方便

下边罗列一下,mongodb中常用的一些函数操作

字符串类型的操作:

$substr,$indexOfBytes,$switch,$strLenBytes,$toLower,$toUpper,$concat,$split,

对数字型字段可以做加减乘除,取余等操作$add,$subtract,$multiply,$divide,$mod

时间类型字段:$year,$month,$week,$hour,$minute,$second,$millsecond,$dayOfYear,$dayOfMonth,$dayOfWeek

如果你创建了多个collection,并且之间有关联关系,你想实现类似mysql的联表查询,你需啊哟使用聚合中的lookup

$lookup操作符可以查找出集合中与另一个集合天剑匹配的文档,此功能类似于关系型数据库中的join查询,此操作符需要以下参数:

from:想要关联的另一个集合

localField: 集合中需要关联的键

foreignField: 与另一个集合关联的键

as: 关联后将另外一个结合的数据嵌入到此字段下

有时候,你需要做一些报表统计,为了查询速度加快,那么你可能需要将聚合的操作放在另一个collection中,平时用定时任务去聚合查询,查询结构汇出到另一个新的collection中,这样以后每次查询只需要从新的collection中普通查询就可以了,那么你需要使用到聚合操作中的out操作符

用$out可以将聚合出来的结果写入到一个指定的结合中,如果指定的集合不存在,则会创建一个新集合,如果指定的集合已存在,则会覆写到此集合中。但如果原有的集合中存在索引,而使用$out写入文档违反此索引,则会写入失败