问题
使用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 后再转化成我想要的对象才成功了
总结
方案一和方案二要选一个的话,从各方面来说我非常推荐方案一,但是方案二是作为无法转化类型的通用解,学习一下也是十分不错的