使用spring mongodb聚合查找文档里数组后使用getMappedResults()出现问题

问题

使用spring mongodb聚合查找文档里数组的一条数据使用getMappedResults()出现问题

解决方案

方案一:

投影字段后加个起个别名,并用该数据的对应类接收

方案二:

转化时先不用该数据的对应类,用mongodb里的BasicDBObject转JSON后再转化成想要的类

问题情景

想在积分表里查找用户的数据后进行修改并放回

import java.util.List;

/**
 * 学习积分表
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Document("study_points_list")
public class StudyPointsList {
    /**
     * 主键
     */
    private String id;

    /**
     * 知识圈id
     */
    @Field("circle_id")
    private String circleId;

    /**
     * 用户积分列表
     */
    private List<UserPoints> list;
}
package org.heiye.test.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.mongodb.core.mapping.Field;

/**
 * 用户积分
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserPoints {
    /**
     * 用户id
     */
    private String uid;

    /**
     * 当前周积分
     */
    @Field("week_points")
    private Integer weekPoints;

    /**
     * 当前月积分
     */
    @Field("month_points")
    private Integer monthPoints;

    /**
     * 总积分
     */
    @Field("sum_points")
    private Integer sumPoints;

    /**
     * 地区
     */
    private String address;
}
void pointsUpdateTest() {
    // 构建聚合条件
    Aggregation aggregation = newAggregation(
            // 展开数组
            unwind("list"),
            // 构建条件
            match(Criteria.where("circle_id").is("1").and("list.uid").is("1")),
            // 投影字段
            project("list").andExclude("_id")
    );

    List<UserPoints> mappedResults = mongoTemplate
                                        .aggregate(aggregation, StudyPointsList.class, UserPoints.class)
                                        .getMappedResults();
    System.out.println(mappedResults);
}

方法结果

[UserPoints(uid=null, dayPoints=null, weekPoints=null, monthPoints=null, sumPoints=null, address=null)]

发现无法转化为我想要的对象类

尝试解决方案

  • 查找Spring Data MongoDB 中文文档 在案例里只有几句描述无法解决
  • 问AI 只有几句描述无法解决
  • CSDN查看大神写的代码,以及一些大佬项目里的代码无法解决
  • 更换本地的mongodb版本尽量接近springboot里面的版本还是无法解决

最后我选择了放弃自动转化成我想要的对象,进行手动转化

(方案一是我在写博客时才发现的呜呜呜)

最终方案

方案一:

直接看代码!!!

void pointsUpdateTest() {
    // 构建聚合条件
    Aggregation aggregation = newAggregation(
            // 展开数组
            unwind("list"),
            // 构建条件
            match(Criteria.where("circle_id").is("1").and("list.uid").is("1")),
            // 投影字段
            project("list").asArray("list").andExclude("_id")
    );

    List<StudyPointsList> mappedResults = mongoTemplate
                                        .aggregate(aggregation, StudyPointsList.class, StudyPointsList.class)
                                        .getMappedResults();

    System.out.println(mappedResults);
    System.out.println(mappedResults.get(0));
    System.out.println(mappedResults.get(0).getList());
}

运行结果

[StudyPointsList(id=null, circleId=null, list=[UserPoints(uid=1, weekPoints=520, monthPoints=520, sumPoints=520, address=广州市)])]

StudyPointsList(id=null, circleId=null, list=[UserPoints(uid=1, weekPoints=520, monthPoints=520, sumPoints=520, address=广州市)])

[UserPoints(uid=1, weekPoints=520, monthPoints=520, sumPoints=520, address=广州市)]

这里最主要的是 project("list").asArray("list") 直接获取到了里面的一个数组并返回了一个基本数据,最后得到了我们想要的结果 UserPoints,只是要剥开一层又一层

方案二:

就是解决方案二里的转化时先不用自身的类,用mongodb里的BasicDBObject转JSON后再转化成想要的类

void pointsUpdateTest() {
    // 构建聚合条件
    Aggregation aggregation = newAggregation(
            // 展开数组
            unwind("list"),
            // 构建条件
            match(Criteria.where("circle_id").is("1").and("list.uid").is("1")),
            // 投影字段
            project("list").andExclude("_id")
    );

    // 得到数据并转化成BasicDBObject
    List<BasicDBObject> mappedResults = mongoTemplate
                                        .aggregate(aggregation, StudyPointsList.class, BasicDBObject.class)
                                        .getMappedResults();
    
    //解析数据
    UserPoints userPoints = MongoAggregateUtil.resolveOne(mappedResults, "list", UserPoints.class);

    System.out.println(userPoints);
}

方法运行结果

UserPoints(uid=1, weekPoints=520, monthPoints=520, sumPoints=520, address=广州市)

附上我写的小工具类

package com.org.oracle.util;

import com.alibaba.fastjson.JSON;
import com.mongodb.BasicDBObject;

import java.util.List;

/**
 * mongodb聚合后解析
 */
public class MongoAggregateUtil {
    /**
     * 聚合后进行解析获得第一个数据
     *
     * @param list 聚合时以BasicDBObject输出的列表数据
     * @param field 需要提取的字段名
     * @param clazz 需要转换的对象
     * @return
     */
    public static <T> T resolveOne(List<BasicDBObject> list, String field, Class<T> clazz) {
        // 得到列表里的第一个数据,获得里面field的value
        String json = JSON.toJSONString(list.get(0).get(field));
        //将得到的json转化为数据对应的对象
        return JSON.parseObject(json, clazz);
    }
}

问题成功解决了,但是为什么别人用 getMappedResults() 畅通无阻而我还要转化,十分不理解,由于其他业务也会用到这个方法我就又想了想,像MySQL直接拿对应类来接如何?不直接用里面的对象接了

探索问题

像上面说的直接用对应类来接收

void pointsUpdateTest() {
    // 构建聚合条件
    Aggregation aggregation = newAggregation(
            // 展开数组
            unwind("list"),
            // 构建条件
            match(Criteria.where("circle_id").is("1").and("list.uid").is("1")),
            // 投影字段
            project("list").andExclude("_id")
    );

    List<StudyPointsList> mappedResults = mongoTemplate
                                        .aggregate(aggregation, StudyPointsList.class, StudyPointsList.class)
                                        .getMappedResults();

    System.out.println(mappedResults);
}

运行结果

Failed to instantiate java.util.List using constructor NO_CONSTRUCTOR with arguments 

报错了!!!!这是一个好的结果报错我们就知道错哪了,但是这报错好像有点不太对啊为什么是报错到 java.util.List 上了 而且说的还是一个没有无参构造的问题

在CSDN上找了两篇文章关于这个问题的

第一篇:Failed to instantiate java.util.List using constructor NO_CONSTRUCTOR with arguments

第二篇:Failed to instantiate java.util.List using constructor NO_CONSTRUCTOR with arguments

第二篇就写到:实体中字段类型是list数组,表中查到的数据,该字段变成了单个对象

看看我的表

import java.util.List;

/**
 * 学习积分表
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Document("study_points_list")
public class StudyPointsList {

    // 省略

    /**
     * 用户积分列表
     */
    private List<UserPoints> list;
}

原来如此,我用mongodb里的BasicDBObject接收

void pointsUpdateTest() {
    // 构建聚合条件
    Aggregation aggregation = newAggregation(
            // 展开数组
            unwind("list"),
            // 构建条件
            match(Criteria.where("circle_id").is("1").and("list.uid").is("1")),
            // 投影字段
            project("list").andExclude("_id")
    );

    List<StudyPointsList> mappedResults = mongoTemplate
                                        .aggregate(aggregation, StudyPointsList.class, StudyPointsList.class)
                                        .getMappedResults();

    System.out.println(mappedResults);
}

运行结果

[{"list": {"uid": "1", "week_points": 520, "month_points": 520, "sum_points": 520, "address": "广州市"}}]

好家伙里面的list还真直接变成了个对象而不是一个数组

我想了想我改变不了mongodb给我的数据,那我直接在类里定义一个对象来接收不就好了吗?

我一开始还在上面添加了(@Transient)但是回来的数据没法识别,我就删去

import java.util.List;

/**
 * 学习积分表
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Document("study_points_list")
public class StudyPointsList {

    // 省略

    /**
     * 用户积分列表
     */
    private List<UserPoints> list;

    private UserPoints item;
}

注意:我这里还把映射(project)的字段起了个别名 "item" 与我上面的 UserPoints 进行映射

void pointsUpdateTest() {
    // 构建聚合条件
    Aggregation aggregation = newAggregation(
            // 展开数组
            unwind("list"),
            // 构建条件
            match(Criteria.where("circle_id").is("1").and("list.uid").is("1")),
            // 投影字段
            project("list").asArray("item").andExclude("_id")
    );

    List<BasicDBObject> mappedResults1 = mongoTemplate
                                        .aggregate(aggregation, StudyPointsList.class, BasicDBObject.class)
                                        .getMappedResults();

    List<StudyPointsList> mappedResults2 = mongoTemplate
                                        .aggregate(aggregation, StudyPointsList.class, StudyPointsList.class)
                                        .getMappedResults();

    System.out.println(mappedResults1);
    System.out.println(mappedResults2);
}

运行结果

[{"item": [{"uid": "1", "week_points": 520, "month_points": 520, "sum_points": 520, "address": "广州市"}]}]

[StudyPointsList(id=null, circleId=null, list=null, item=null)]

这...也不行???我就不信了我要看看你里面的BasicDBObject是个什么东西

分歧

方案一:

其实在这里就可以发现我用了project().asArray()后,里面 item 变成了数组而不是一个对象

也就是说我把 project().asArray("itme") 换回成 project().asArray("list")后 就可以对应上我 该数据的对应类里的 list 了

import java.util.List;

/**
 * 学习积分表
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Document("study_points_list")
public class StudyPointsList {

    // 省略

    /**
     * 用户积分列表
     */
    private List<UserPoints> list;
}

可这已经是后话了,还是在我写这个博客时发现的 呜呜呜

方案二:

这里我以为错了就把 .asArray("itme") 删掉了

void pointsUpdateTest() {
    // 构建聚合条件
    Aggregation aggregation = newAggregation(
            // 展开数组
            unwind("list"),
            // 构建条件
            match(Criteria.where("circle_id").is("1").and("list.uid").is("1")),
            // 投影字段
            project("list").asArray("item").andExclude("_id")
    );

    List<BasicDBObject> mappedResults1 = mongoTemplate
                                        .aggregate(aggregation, StudyPointsList.class, BasicDBObject.class)
                                        .getMappedResults();

    System.out.println(mappedResults);
    System.out.println(mappedResults.get(0));
    System.out.println(mappedResults.get(0).get("list"));
}

运行结果

[{"list": {"uid": "1", "week_points": 520, "month_points": 520, "sum_points": 520, "address": "广州市"}}]

{"list": {"uid": "1", "week_points": 520, "month_points": 520, "sum_points": 520, "address": "广州市"}}

Document{{uid=1, week_points=520, month_points=520, sum_points=520, address=广州市}}

噢噢噢 原来是个Document(与BasicDBObject一样是mongodb提供的类)

小小Document看看我把你强转拿下

说时迟那时快 我一把抓住 Document 即刻炼化成我想要的类 UserPoints

void pointsUpdateTest() {
    // 构建聚合条件
    Aggregation aggregation = newAggregation(
            // 展开数组
            unwind("list"),
            // 构建条件
            match(Criteria.where("circle_id").is("1").and("list.uid").is("1")),
            // 投影字段
            project("list").andExclude("_id")
    );

    List<BasicDBObject> mappedResults1 = mongoTemplate
                                        .aggregate(aggregation, StudyPointsList.class, BasicDBObject.class)
                                        .getMappedResults();

    System.out.println(mappedResults);
    System.out.println(mappedResults.get(0));
    System.out.println(mappedResults.get(0).get("list"));

    UserPoints userPoints = (UserPoints) mappedResults.get(0).get("list");

    System.out.println(userPoints);
}

运行结果 

[{"list": {"uid": "1", "week_points": 520, "month_points": 520, "sum_points": 520, "address": "广州市"}}]

{"list": {"uid": "1", "week_points": 520, "month_points": 520, "sum_points": 520, "address": "广州市"}}

Document{{uid=1, week_points=520, month_points=520, sum_points=520, address=广州市}}

java.lang.ClassCastException: class org.bson.Document cannot be cast to class org.heiye.test.entity.UserPoints (org.bson.Document and org.heiye.test.entity.UserPoints are in unnamed module of loader 'app')

	at org.heiye.test.OracleApplicationTests.pointsUpdateTest(OracleApplicationTests.java:55)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)

好好好 报错老实了 意思就是无法强转成你想要的类(没让您尽兴真是抱歉)

我又不信了,那你里面相同名称下的值我可以复制吧,我掏出我BeanUtils.copyProperties()

void pointsUpdateTest() {
    // 构建聚合条件
    Aggregation aggregation = newAggregation(
            // 展开数组
            unwind("list"),
            // 构建条件
            match(Criteria.where("circle_id").is("1").and("list.uid").is("1")),
            // 投影字段
            project("list").andExclude("_id")
    );

    List<BasicDBObject> mappedResults1 = mongoTemplate
                                        .aggregate(aggregation, StudyPointsList.class, BasicDBObject.class)
                                        .getMappedResults();

    System.out.println(mappedResults);
    System.out.println(mappedResults.get(0));
    System.out.println(mappedResults.get(0).get("list"));
    System.out.println("------------------------------");

    Object obj = mappedResults.get(0).get("list");

    UserPoints userPoints = new UserPoints();

    // 复制相同名称下的值到userPoints中
    BeanUtils.copyProperties(obj, userPoints);

    System.out.println(userPoints);
}

运行结果

[{"list": {"uid": "1", "week_points": 520, "month_points": 520, "sum_points": 520, "address": "广州市"}}]

{"list": {"uid": "1", "week_points": 520, "month_points": 520, "sum_points": 520, "address": "广州市"}}

Document{{uid=1, week_points=520, month_points=520, sum_points=520, address=广州市}}

------------------------------

UserPoints(uid=null, weekPoints=null, monthPoints=null, sumPoints=null, address=null)

wdf? 其他驼峰命名的我不管,但是uid 和address 也没复制进去就离谱了

最后的最后,就如我写的工具类里将这个Document 转化成 JSON 后再转化成我想要的对象才成功了

总结

方案一方案二选一个的话,从各方面来说我非常推荐方案一,但是方案二是作为无法转化类型的通用解,学习一下也是十分不错的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值