赶集网天津网站建设,做网站自己能做百度推广吗,有赞微商城登录入口,轻云服务器 多个网站文章目录 And Or迷惑原因 告别InvalidMongoDbApiUsageException问题简单解决根本解决修改源码 代码(省流#xff0c;可以直接看这里#xff09; And Or
很多时候都需要进行逻辑的与或操作#xff0c;但是spring当中自带的操作并不好用#xff0c;于是做了相关的改进#… 文章目录 And Or迷惑原因 告别InvalidMongoDbApiUsageException问题简单解决根本解决修改源码 代码(省流可以直接看这里 And Or
很多时候都需要进行逻辑的与或操作但是spring当中自带的操作并不好用于是做了相关的改进首先来看原本的操作
// or操作
Criteria criteriaA new Criteria();
criteriaA.orOperator(Criteria.where(name).is(wang),Criteria.where(age).is(18)
);// and操作 这里为了图省事直接用了一样的条件只有方法不同
Criteria criteriaB new Criteria();
criteriaB.andOperator( // 这里跟上面不一样用的是andOperatorCriteria.where(name).is(wang),Criteria.where(age).is(18)
);迷惑
这里存在一个问题就是当我们试图将这两个Criteria合并起来查询的时候就会报错
Query query new Query();
query.addCriteria(criteriaA);
query.addCriteria(criteriaB);
System.out.println(query);/* 报错如下
org.springframework.data.mongodb.InvalidMongoDbApiUsageException: Due to limitations of the com.mongodb.BasicDocument, you cant add a second null criteria. Query already contains { $or : [{ name : wang}, { age : 18}]}
*/这就非常迷惑了报错内容是我们不能添加两个键值为null的criteria可是我们添加的明明是$or和$and。要解释这个问题要深入解析一下Criteria的构成。
原因
Criteria其中有四个属性
private String key;
private ListCriteria criteriaChain;
private LinkedHashMapString, Object criteria new LinkedHashMap();
private Object isValue;key 表示键值criteriaChain 是一个链表存储了其他筛选的条件当执行criteria.and().is()操作的时候就会添加在其中criteria 是一个Map存储了除了is以外的筛选条件例如gt/ne之类的isValue 存放了criteria.is()方法设置的值 这里如果我们采用无参构造方法new Criteria()
public Criteria() {this.isValue NOT_SET;this.criteriaChain new ArrayList();
}就会造成key值为空null当我们将两个Criteria合并起来查询的时候就会报错了。 解决方法 最简单的解决方法就是只使用一次new Criteria()方法
Criteria criteriaA new Criteria();
criteriaA.orOperator(Criteria.where(name).is(wang),Criteria.where(age).is(18)
);
criteriaA.andOperator(Criteria.where(name).is(wang),Criteria.where(age).is(18)
);
Query query new Query();
query.addCriteria(criteriaA);
System.out.println(query);如果是逻辑不复杂的话那么这样就OK了。但是这里仍然埋了一个雷那就是criteriaA的键值仍然为空很有可能在其他地方报错。让我们冷静分析一下这里问题的根源是没有一个静态方法可以直接创造一个and/or类型的Criteria对象OK下面来继续深入探索一下为什么没有静态方法呢。 我们点开orOperator方法看看他怎么实现的
public Criteria orOperator(CollectionCriteria criteria) {Assert.notNull(criteria, Criteria must not be null!);BasicDBList bsonList this.createCriteriaList(criteria);return this.registerCriteriaChainElement((new Criteria($or)).is(bsonList));
}我们会发现一个大无语事件这东西就是单纯地将$or作为键值重新构造了一个Criteria对象然后续到了原有criteriaChain的后面所以从技术上来说完全没有问题啊开发者就是懒得没写$or/$and的静态方法那我们自己来写好了
public class CriteriaUtil{public static Criteria and(Criteria... criteria){return and(Arrays.asList(criteria));}public static Criteria and(CollectionCriteria criteria) {BasicDBList bsonList createCriteriaList(criteria);return new Criteria($and).is(bsonList);}public static Criteria or(Criteria... criteria){return or(Arrays.asList(criteria));}public static Criteria or(CollectionCriteria criteria){BasicDBList bsonList createCriteriaList(criteria);return new Criteria($or).is(bsonList);}private static BasicDBList createCriteriaList(CollectionCriteria criteria) {BasicDBList bsonList new BasicDBList();for (Criteria c : criteria) {bsonList.add(c.getCriteriaObject());}return bsonList;}
}
顺便一提上面的createCriteriaList是从源码里面抄过来的以及源码这个方法没有写成静态的还写成私有的了
Criteria criteriaA CriteriaUtil.or(Criteria.where(name).is(wang),Criteria.where(age).is(18)
);
Criteria criteriaB CriteriaUtil.and(Criteria.where(name).is(wang),Criteria.where(age).is(18)
);
Query query new Query();
query.addCriteria(criteriaA);
query.addCriteria(criteriaB);
System.out.println(query);
至此问题得以优雅地解决。多说一句为什么要采用这么复杂的解决方法呢因为我的项目当中有很多criteriaA/B/C/D并且中间代码跨度很大修改Criteria本身反倒简单。 下面通过反射机制来重写Criteria。
告别InvalidMongoDbApiUsageException
问题
在绝大部分情况下如果我们对同一个字段设置了不同的条件我们都是希望这些条件同时要满足比如一下例子
Criteria criteria Criteria.where(A).gt(100);
criteria.and(B).is(b);
... ...criteria.and(A).lt(200);首先我们设置了“A100”的条件经过一些判断之后我们又需要添加“A200”的条件但这样是会运行报错的 InvalidMongoDbApiUsageException: Due to limitations of the org.bson.Document, you can’t add a second ‘A’ expression specified as ‘A : Document{{KaTeX parse error: Expected EOF, got } at position 7: lt200}̲}. Criteria al…gt100}}’.
简单解决
最简单的解决办法就是合并两个条件在代码当中写到一起
Criteria criteria Criteria.where(A).gt(100).lt(200);
criteria.and(B).is(b);
System.out.println(Query.query(criteria));结果如下
Query: { A : { $gt : 100, $lt : 200}, B : b}, Fields: {}, Sort: {}但这样调整了我们条件的顺序要求我们必须将对同一字段的筛选条件写在一起不能分开。这是因为在Spring框架当中使用MongoDB时对于某个字段的条件筛选只能设置一次设置完成之后才能设置其他字段的条件。并且这种情况对于$and/$or也是一样的 比如两个$or
Criteria criteria new Criteria();
criteria.orOperator(Criteria.where(A).is(a),Criteria.where(B).is(b)
);
criteria.orOperator(Criteria.where(C).is(c),Criteria.where(D).is(d)
);
System.out.println(Query.query(criteria));对于这种问题的简单解决办法是用and将两个or操作包起来
Criteria criteriaA new Criteria();
criteriaA.orOperator(Criteria.where(A).is(a),Criteria.where(B).is(b)
);Criteria criteriaB new Criteria();
criteriaB.orOperator(Criteria.where(C).is(c),Criteria.where(D).is(d)
);
Criteria criteria new Criteria();
criteria.andOperator(criteriaA,criteriaB);
System.out.println(Query.query(criteria));
根本解决
简单的解决办法其实就是把不同的条件写到一起或者用and包起来。但实际情况当中我们很可能需要在已经生成的Criteria上面继续添加条件这时如果之前对某个字段设置过条件就无法再次添加条件了或者将二者用很多$and包起来。为了优雅的处理这种情况我们需要深入源码来探究一下。 Criteria对象是通过getCriteriaObject方法转化为Document的来生成MongoDB当中可执行的语句
public Document getCriteriaObject() {if (this.criteriaChain.size() 1) {return ((Criteria)this.criteriaChain.get(0)).getSingleCriteriaObject();} else if (CollectionUtils.isEmpty(this.criteriaChain) !CollectionUtils.isEmpty(this.criteria)) {return this.getSingleCriteriaObject();} else {Document criteriaObject new Document();Iterator var2 this.criteriaChain.iterator();while(var2.hasNext()) {Criteria c (Criteria)var2.next();Document document c.getSingleCriteriaObject();Iterator var5 document.keySet().iterator();while(var5.hasNext()) {String k (String)var5.next();this.setValue(criteriaObject, k, document.get(k));}}return criteriaObject;}
}上面这个函数不用细看其中的setValue方法是抛出异常的关键
private void setValue(Document document, String key, Object value) {Object existing document.get(key);if (existing null) {document.put(key, value);} else {throw new InvalidMongoDbApiUsageException(Due to limitations of the org.bson.Document, you cant add a second key expression specified as key : value . Criteria already contains key : existing .);}
}逻辑非常简单了首先判断Document当中有没有key值没有的话就插入key,value有的话就报错。那么解决的方法也就有了如果往Document中插入时起了冲突那么就用$and将二者进行合并。
修改源码
修改完成的setValue函数如下
private static void setValue(Document document, String key, Object value) {Object existing document.get(key);if (existing null) {document.put(key, value);} else {if(key.equals($and)){if(value.getClass() ! BasicDBList.class || existing.getClass() ! BasicDBList.class){throw new InvalidMongoDbApiUsageException(error: $and meet unknown type value.getClass() existing.getClass());}BasicDBList basicDBList new BasicDBList();basicDBList.addAll((BasicDBList) existing);basicDBList.addAll((BasicDBList) value);document.put(key,basicDBList);}else{document.remove(key);Document left new Document(key,existing);Document right new Document(key,value);BasicDBList basicDBList new BasicDBList();basicDBList.add(left);basicDBList.add(right);setValue(document,$and,basicDBList);}}
}但是我们不能直接修改包当中的源码这里我采用的方法是新建一个类CriteriaSub并拷贝了Criteria的部分方法用Java的反射机制将Criteria中的私有变量读取了出来。最终实现了将Criteria生成Document的方法并保证不会报错InvalidMongoDbApiUsageException然后新写一个方法将Document再次还原成Criteria以便后续使用。
代码(省流可以直接看这里
最终所有代码如下建立一个工具类CriteriaUtil 对于原本出错的语句加上这一行即可
criteria CriteriaUtil.reform(criteria);import com.mongodb.BasicDBList;
import lombok.extern.slf4j.Slf4j;
import org.bson.Document;
import org.springframework.data.mongodb.InvalidMongoDbApiUsageException;
import org.springframework.data.mongodb.core.geo.GeoJson;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.GeoCommand;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;import java.lang.reflect.Field;
import java.util.*;/*** Description Criteria重构*/
public class CriteriaUtil{private static BasicDBList createCriteriaList(CollectionCriteria criteria) {BasicDBList bsonList new BasicDBList();for (Criteria c : criteria) {bsonList.add(c.getCriteriaObject());}return bsonList;}public static Criteria and(Criteria... criteria){return and(Arrays.asList(criteria));}public static Criteria and(CollectionCriteria criteria) {BasicDBList bsonList createCriteriaList(criteria);return new Criteria($and).is(bsonList);}public static Criteria or(Criteria... criteria){return or(Arrays.asList(criteria));}public static Criteria or(CollectionCriteria criteria){BasicDBList bsonList createCriteriaList(criteria);return new Criteria($or).is(bsonList);}/*** Description: 重组criteria解决InvalidMongoDbApiUsageException*/public static Criteria reform(Criteria criteria){try {criteria.getCriteriaObject();return criteria;} catch (InvalidMongoDbApiUsageException e) {Document document new CriteriaSub(criteria).getCriteriaObject();return from(document);}}/*** Description: Document 转化成 Criteria*/public static Criteria from(Document document) {Criteria c new Criteria();try {Field _criteria c.getClass().getDeclaredField(criteria);_criteria.setAccessible(true);SuppressWarnings(unchecked)LinkedHashMapString, Object criteria (LinkedHashMapString, Object) _criteria.get(c);for (Map.EntryString, Object set : document.entrySet()) {criteria.put(set.getKey(), set.getValue());}Field _criteriaChain c.getClass().getDeclaredField(criteriaChain);_criteriaChain.setAccessible(true);SuppressWarnings(unchecked)ListCriteria criteriaChain (ListCriteria) _criteriaChain.get(c);criteriaChain.add(c);} catch (Exception e) {// Ignore}return c;}private static class CriteriaSub {private static Object NOT_SET new Object();private static final int[] FLAG_LOOKUP new int[\uffff];Nullableprivate String key;private ListCriteria criteriaChain;private LinkedHashMapString, Object criteria new LinkedHashMap();Nullableprivate Object isValue;public CriteriaSub(Criteria criteria){Class? clazz criteria.getClass();Field field ;try {field clazz.getDeclaredField(NOT_SET);field.setAccessible(true);//压制java检查机制NOT_SET field.get(criteria);field clazz.getDeclaredField(key);field.setAccessible(true);//压制java检查机制key (String) field.get(criteria);field clazz.getDeclaredField(criteriaChain);field.setAccessible(true);//压制java检查机制criteriaChain (ListCriteria) field.get(criteria);field clazz.getDeclaredField(criteria);field.setAccessible(true);//压制java检查机制this.criteria (LinkedHashMapString, Object) field.get(criteria);field clazz.getDeclaredField(isValue);field.setAccessible(true);//压制java检查机制isValue field.get(criteria);} catch (NoSuchFieldException | IllegalAccessException e) {// ignore}}public Document getCriteriaObject() {if (this.criteriaChain.size() 1) {return new CriteriaSub(this.criteriaChain.get(0)).getSingleCriteriaObject();} else if (CollectionUtils.isEmpty(this.criteriaChain) !CollectionUtils.isEmpty(this.criteria)) {return this.getSingleCriteriaObject();} else {Document criteriaObject new Document();for (Criteria value : this.criteriaChain) {CriteriaSub c new CriteriaSub(value);Document document c.getSingleCriteriaObject();for (String k : document.keySet()) {setValue(criteriaObject, k, document.get(k));}}return criteriaObject;}}protected Document getSingleCriteriaObject() {Document document new Document();boolean not false;for (Map.EntryString, Object stringObjectEntry : this.criteria.entrySet()) {String key stringObjectEntry.getKey();Object value stringObjectEntry.getValue();if (requiresGeoJsonFormat(value)) {value new Document($geometry, value);}if (not) {Document notDocument new Document();notDocument.put(key, value);document.put($not, notDocument);not false;} else if ($not.equals(key) value null) {not true;} else {document.put(key, value);}}if (!StringUtils.hasText(this.key)) {if (not) {return new Document($not, document);}return document;}Document queryCriteria new Document();if (!NOT_SET.equals(this.isValue)) {queryCriteria.put(this.key, this.isValue);queryCriteria.putAll(document);} else {queryCriteria.put(this.key, document);}return queryCriteria;}private static boolean requiresGeoJsonFormat(Object value) {return value instanceof GeoJson || value instanceof GeoCommand ((GeoCommand)value).getShape() instanceof GeoJson;}private static void setValue(Document document, String key, Object value) {Object existing document.get(key);if (existing null) {document.put(key, value);} else {if(key.equals($and)){System.out.println(merge $and value existing);if(value.getClass() ! BasicDBList.class || existing.getClass() ! BasicDBList.class){throw new InvalidMongoDbApiUsageException(error: $and meet unknown type value.getClass() existing.getClass());}BasicDBList basicDBList new BasicDBList();basicDBList.addAll((BasicDBList) existing);basicDBList.addAll((BasicDBList) value);document.put(key,basicDBList);}else{System.out.println(merge key value existing);document.remove(key);Document left new Document(key,existing);Document right new Document(key,value);BasicDBList basicDBList new BasicDBList();basicDBList.add(left);basicDBList.add(right);setValue(document,$and,basicDBList);
// throw new InvalidMongoDbApiUsageException(Due to limitations of the org.bson.Document, you cant add a second key expression specified as key : value . Criteria already contains key : existing .);}}}}
}