1 代码
1.1 查询条件构建基础类
设计用来支持流式查询,以及增强语义化的一些类
package xyz.yq56.mongo.entity.query;
import java.util.HashMap;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* 精确查询基础类,理论上下面范围和排序也可以用这个代替,但是语义很差
* @author yiqiang
*/
public class ChainMap extends HashMap<String, Object> {
public static ChainMap build() {
return new ChainMap();
}
public ChainMap add(String key, Object value) {
this.put(key, value);
return this;
}
@Override
public String toString() {
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(this);
} catch (JsonProcessingException e) {
return "";
}
}
}
package xyz.yq56.mongo.entity.query;
import java.util.Arrays;
import java.util.HashMap;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* 排序基础类
* @author yi qiang
* @date 2022/8/7 16:23
*/
public class ChainOrder extends HashMap<String, String> {
public static final String DESC = "DESC";
public static final String ASC = "ASC";
public static ChainOrder build() {
return new ChainOrder();
}
/**
* 添加排序
*
* @param key 排序字段
* @param value 顺序: asc or desc
* @return 排序封装类
*/
public ChainOrder add(String key, String value) {
if (!Arrays.asList(DESC, ASC).contains(value.toUpperCase())) {
throw new IllegalArgumentException();
}
this.put(key, value.toUpperCase());
return this;
}
@Override
public String toString() {
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(this);
} catch (JsonProcessingException e) {
return "";
}
}
}
package xyz.yq56.mongo.entity.query;
import java.util.HashMap;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 范围查询基础类
* @author yi qiang
* @date 2022/8/7 15:10
*/
public class ChainRange extends HashMap<String, ChainRange.Range> {
public static ChainRange build() {
return new ChainRange();
}
public ChainRange add(String key, Range value) {
this.put(key, value);
return this;
}
@Data
@Accessors(chain = true)
public static class Range {
//左边是否是开区间,默认是闭区间
private boolean leftOpen;
//右边是否是开区间,默认是闭区间
private boolean rightOpen;
//左边值
private Object left;
//右边值
private Object right;
}
@Override
public String toString() {
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(this);
} catch (JsonProcessingException e) {
return "";
}
}
}
1.2 查询条件构建类MongoParam
设计出来其实主要功能就是build方法来构建Query对象,其内部封装了多条件判空,offset计算以及多条件排序等功能.理论上,只要创建好MongoParam,然后调用build方法即可得到想要的查询结果
另外也贴心的准备了buildForCount方法,用来给到count方法使用
package xyz.yq56.mongo.entity.query;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* @author yiqiang
*/
@Accessors(chain = true)
@Data
@SuppressWarnings("all")
public class MongoParam {
/**
* 相等的字段
*/
ChainMap eqFields;
/**
* 范围查询字段
*/
ChainRange rangeFields;
/**
* 排序字段
*/
ChainOrder orderFields;
/**
* 偏移量
*/
Long offset;
/**
* 页码
*/
Long page;
/**
* 页面大小
*/
Integer size;
/**
* 构建查询条件
*
* @return 返回MongoTemplate所需查询条件
*/
public Query build() {
Query query = new Query();
query.addCriteria(createCriteria());
withOrder(query);
withPage(query);
return query;
}
/**
* 为计数构建Query,不需要排序和分页等字段逻辑
* <br> 已浅拷贝对象,不会影响build()结果
*
* @return 查询对象
*/
public Query buildForCount() {
MongoParam params = new MongoParam();
BeanUtils.copyProperties(this, params);
params.orderFields = null;
params.offset = null;
params.page = null;
params.size = null;
return params.build();
}
/**
* 仅仅只会给query添加分页相关属性,如skip和limit
*
* @param query 查询
*/
public Query withPage(Query query) {
if (query == null) {
return null;
}
if (size != null) {
query.limit(size);
if (page != null) {
query.skip((Math.max(page - 1, 0) * size));
}
}
//优先offset
if (offset != null) {
query.skip(offset);
}
return query;
}
/**
* 仅仅只会给query添加排序相关属性
*
* @param query 查询
*/
public Query withOrder(Query query) {
if (query == null) {
return null;
}
//排序Map不为空,那么构建排序字段
if (orderFields != null && orderFields.size() > 0) {
List<Sort.Order> orderList = new ArrayList<>();
//key是待排序字段;value是DESC或ASC字符串,大小写不敏感;
orderFields.forEach((key, value) -> orderList.add(new Sort.Order(Sort.Direction.valueOf(value), key)));
query.with(Sort.by(orderList));
}
return query;
}
/**
* 获取到查询相关属性,包含精确查询和范围查询
*
* @return 基准
*/
public Criteria createCriteria() {
Criteria criteria = new Criteria();
//精确匹配字段条件构建
if (eqFields != null && eqFields.size() > 0) {
eqFields.forEach((key, value) -> {
if (value != null) {
if (value instanceof String && ((String) value).length() <= 0) {
return;
}
if (value instanceof Collection && ((Collection) value).isEmpty()) {
return;
}
if (value instanceof Map && ((Map) value).isEmpty()) {
return;
}
criteria.and(key).is(value);
}
});
}
//范围查询
if (rangeFields != null && rangeFields.size() > 0) {
rangeFields.forEach((key, value) -> {
Criteria keyCriteria = criteria.and(key);
Criteria leftCriteria = leftCriteria(keyCriteria, value);
//如果左边条件不存在,那就直接检查右边条件
if (leftCriteria != null) {
rightCriteria(leftCriteria, value);
} else {
rightCriteria(keyCriteria, value);
}
});
}
return criteria;
}
private Criteria leftCriteria(Criteria criteria, ChainRange.Range range) {
if (range.getLeft() != null) {
if (range.isLeftOpen()) {
return criteria.gt(range.getLeft());
} else {
return criteria.gte(range.getLeft());
}
}
return null;
}
private Criteria rightCriteria(Criteria criteria, ChainRange.Range range) {
if (range.getRight() != null) {
if (range.isRightOpen()) {
return criteria.lt(range.getRight());
} else {
return criteria.lte(range.getRight());
}
}
return null;
}
@Override
public String toString() {
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(this);
} catch (JsonProcessingException e) {
return "";
}
}
}
1.3 查询工具类
package xyz.yq56.mongo.util;
import java.util.List;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import xyz.yq56.mongo.entity.query.MongoParam;
/**
* @author yi qiang
* @date 2022/8/7 4:43
*/
public class MongoUtil {
private MongoUtil() {
}
public static <T> List<T> list(MongoTemplate mongoTemplate, MongoParam mongoParam, Class<T> zClass) {
return mongoTemplate.find(mongoParam.build(), zClass);
}
}
2 使用示例
2.1 测试代码
@Repository
@Slf4j
public class RoomBonusDailyDataDaoMongoImpl {
@Autowired
MongoTemplate mongoTemplate;
public List<RoomBonusDailyData> test() {
Date left = new Date(System.currentTimeMillis() - (60 * 1000 * 60 * 24) * 10);
MongoParam mongoParam = new MongoParam()
.setEqFields(ChainMap.build()
.add("uid", "752")
.add("roomId", "2")
)
.setRangeFields(ChainRange.build()
.add("updateTime", new ChainRange.Range().setLeft(left))
.add("type", new ChainRange.Range().setLeft(1).setRight(2))
)
.setOrderFields(ChainOrder.build()
.add("updateTime", "ASC")
.add("uid", "DESC"))
.setPage(1L)
.setSize(10);
System.out.println(mongoParam);
return MongoUtil.list(mongoTemplate, mongoParam, RoomBonusDailyData.class);
}
}
2.2 输出结果
从发送的command可以看到所有的条件都已生效
{
"eqFields" : {
"uid" : "752",
"roomId" : "2"
},
"rangeFields" : {
"updateTime" : {
"leftOpen" : false,
"rightOpen" : false,
"left" : 1659021724377,
"right" : null
},
"type" : {
"leftOpen" : false,
"rightOpen" : false,
"left" : 1,
"right" : 2
}
},
"orderFields" : {
"uid" : "DESC",
"updateTime" : "ASC"
},
"offset" : null,
"page" : 1,
"size" : 10
}
2022-08-07 23:22:04.440 DEBUG 13152 --- [ main] o.s.data.mongodb.core.MongoTemplate : find using query: { "uid" : "752", "roomId" : "2", "updateTime" : { "$gte" : { "$date" : 1659021724377}}, "type" : { "$gte" : 1, "$lte" : 2}} fields: Document{{}} for class: class xyz.yq56.mongo.entity.RoomBonusDailyData in collection: roomBonusDailyData
2022-08-07 23:22:04.466 INFO 13152 --- [ main] org.mongodb.driver.connection : Opened connection [connectionId{localValue:2, serverValue:269}] to localhost:27017
2022-08-07 23:22:04.478 DEBUG 13152 --- [ main] org.mongodb.driver.protocol.command : Sending command '{"find": "roomBonusDailyData", "filter": {"uid": "752", "roomId": "2", "updateTime": {"$gte": {"$date": "2022-07-28T15:22:04.377Z"}}, "type": {"$gte": 1, "$lte": 2}}, "sort": {"uid": -1, "updateTime": 1}, "limit": 10, "$db": "test"}' with request id 6 to database test on connection [connectionId{localValue:2, serverValue:269}] to server localhost:27017
2022-08-07 23:22:04.478 DEBUG 13152 --- [ main] org.mongodb.driver.protocol.command : Execution of command with request id 6 completed successfully in 4.66 ms on connection [connectionId{localValue:2, serverValue:269}] to server localhost:27017
[RoomBonusDailyData(id=2-2-20220801, type=2, roomId=2, uid=752, region=6, value=529, weekStartDate=20220801, updateTime=Mon Aug 01 22:47:18 CST 2022)]