通过配置的方式实现类似SQL方式查询MongoDB

背景说明

对于Mysql,有灵活的sql语句直接执行,且sql语句对于程序来说即一个字符串,很方便扩展或变换,即使是有变量,也可以通过字符串操作直接实现替换,最重要的是sql结构比较简单易懂,而且天生适合关联查询。而MongoDB操作起来就不那么方便,没有sql,必须同时熟悉javascript语言语法和mongo命令、参数,如果不熟悉的话,则要在代码里写死查询框架,不方便修改和扩展。这里通过java实现了一个类似sql的语言框架,采取json格式,以方便通过配置的方式实现mongo查询。

代码实现

查询主框架

    private JSON doGet(MongoTemplate mongoTemplate, JSONObject nosql, Map<String, String> parameterMap) throws Exception {
        if (isSimpleType(nosql.getString("type"))) return doGetSimple(mongoTemplate, nosql, parameterMap);
        else return doGetComplex(mongoTemplate, nosql, parameterMap);
    }

    private Boolean isSimpleType(String type) {
        return StringUtils.isEmpty(type) || type.equals("simple");
    }

简单查询框架

   private JSON doGetSimple(MongoTemplate mongoTemplate, JSONObject nosql, Map<String, String> parameterMap) throws Exception {
        String condition = StringUtility.rebuildQuery(nosql.getString("condition"), parameterMap);
        log.debug("condition: " + condition);
        Criteria criteria = parseCondition(parseCondition(condition));
        Query query = (criteria == null) ? new Query() : new Query(criteria);
        if (nosql.containsKey("sorts")) {
            JSONArray sorts = nosql.getJSONArray("sorts");
            if (sorts.size() > 0) {
                List<Sort.Order> orders = new ArrayList<Sort.Order>();
                for (Object sort : sorts) {
                    Direction direction = ((JSONObject) sort).getString("direction").equals("asc") ? Direction.ASC : Direction.DESC;
                    orders.add(new Sort.Order(direction, ((JSONObject) sort).getString("name")));
                }
                query = query.with(Sort.by(orders));
            }
        }
        if (nosql.containsKey("skip")) query = query.skip(nosql.getLong("skip"));
        if (nosql.containsKey("limit")) query = query.limit(nosql.getInteger("limit"));
        List<JSONObject> results = mongoTemplate.find(query, JSONObject.class, nosql.getString("collection_name"));
        List<Object> collection = doSubGet(mongoTemplate, nosql, parameterMap, results);
        return postGet(nosql, collection);
    }

解析字符串条件

    private JSONObject parseCondition(String condition) throws Exception {
        condition = condition.trim();
        if (condition.startsWith("{")) return parseConditionJoint(condition);
        else return parseConditionOne(condition);
    }

解析连接词

    private JSONObject parseConditionJoint(String condition) throws Exception {
        JSONObject result = null;
        int closeIndex = -1, count = 0;
        for (int i = 0; i < condition.length(); i++) {
            if (condition.charAt(i) == '{') {
                count++;
            } else if (condition.charAt(i) == '}') {
                if (count < 1) {
                    throw new Exception("Error condition expression! Near: " + condition.substring(i));
                } else if (count == 1) {
                    count--;
                    closeIndex = i;
                    result = parseCondition(condition.substring(1, closeIndex));
                    condition = condition.substring(closeIndex + 1).trim();
                    if (condition.isEmpty()) {
                        break;
                    } else if (condition.startsWith("||") || condition.startsWith("&&")) {
                        result = new JSONObject().fluentPut("left", result.clone()).fluentPut("symbol", condition.substring(0, 2)).fluentPut("right",
                                parseCondition(condition.substring(2)));
                        break;
                    } else {
                        throw new Exception("Error condition expression! Near: " + condition.substring(i));
                    }
                } else {
                    count--;
                }
            }
        }
        if (count > 0) throw new Exception("Error condition expression! {} mismatching!");
        return result;
    }

解析单个查询条件表达式

    private JSONObject parseConditionOne(String condition) throws Exception {
        JSONObject result = new JSONObject();
        if (StringUtils.isEmpty(condition)) return result;
        String symbol = null;
        if (checkNotOpCondition(condition, " in ")) {
            symbol = "in";
            result.fluentPut("right", JSON.parseArray(condition.substring(condition.indexOf(" in ") + 4).trim(), Object.class));
        } else if (checkNotOpCondition(condition, " nin ")) {
            symbol = "nin";
            result.fluentPut("right", JSON.parseArray(condition.substring(condition.indexOf(" nin ") + 5).trim(), Object.class));
        } else if (checkNotOpCondition(condition, " regex ")) {
            symbol = "regex";
            result.fluentPut("right", JSON.parseObject(condition.substring(condition.indexOf(" regex ") + 7).trim(), String.class));
        }
        if (symbol != null) result.fluentPut("left", condition.substring(0, condition.indexOf(symbol)).trim()).fluentPut("symbol", symbol);
        else result.fluentPut("exp", expCondition(condition)).fluentPut("symbol", "exp");
        return result;
    }

判断是否为非运算(exp)条件

    private Boolean checkNotOpCondition(String condition, String symbol) {
        if (!(condition.contains(symbol))) return false;
        Integer leftEnd = condition.indexOf(symbol);
        if (!(StringUtility.isJavaIdentifier(condition.substring(0, leftEnd).trim()))) return false;
        return true;
    }

处理运算条件

    private String expCondition(String condition) {
        if (StringUtils.isEmpty(condition)) return "";
        int i, j;
        for (i = 0; i < condition.length(); i++) {
            if (condition.charAt(i) == '\"' || condition.charAt(i) == '\'') {
                Character character = condition.charAt(i);
                for (j = i + 1; j < condition.length() && (condition.charAt(j) != character || condition.charAt(j - 1) == '\\'); j++);
                i = j + 1;
            } else if (Character.isJavaIdentifierStart(condition.charAt(i))) {
                j = condition.indexOf(' ', i);
                j = j > 0 ? j : condition.length();
                if (condition.substring(i, j).matches("null|false|true")) {
                    i = j;
                } else {
                    condition = condition.substring(0, i) + "this." + condition.substring(i);
                    i = j + 5;
                }
            }
        }
        return condition;
    }

解析JSON条件

    private Criteria parseCondition(JSONObject condition) {
        log.debug("condition: " + condition);
        if (condition == null || condition.getString("symbol") == null) return null;
        switch (condition.getString("symbol")) {
        case "||":
            return parseCondition(condition.getJSONObject("left")).orOperator(parseCondition(condition.getJSONObject("right")));
        case "&&":
            return parseCondition(condition.getJSONObject("left")).andOperator(parseCondition(condition.getJSONObject("right")));
        case "in":
            return new Criteria(condition.getString("left")).in(condition.get("right"));
        case "nin":
            return new Criteria(condition.getString("left")).nin(condition.get("right"));
        case "regex":
            return new Criteria(condition.getString("left")).regex(condition.getString("right"));
        case "exp":
            return new Criteria() {
                @Override
                public Document getCriteriaObject() {
                    Document document = new Document();
                    document.put("$where", condition.getString("exp"));
                    return document;
                }
            };
        default:
            return null;
        }
    }

复杂查询框架

    private JSON doGetComplex(MongoTemplate mongoTemplate, JSONObject nosql, Map<String, String> parameterMap) throws Exception {
        String pipeline = StringUtility.rebuildQuery(nosql.getString("pipeline"), parameterMap);
        String command = "{\"aggregate\":\"" + nosql.getString("collection_name") + "\",\"pipeline\":" + pipeline + ",\"cursor\":{}}";
        List<Document> documents = ((Document) (mongoTemplate.executeCommand(command).get("cursor"))).getList("firstBatch", Document.class);
        List<JSONObject> results = new ArrayList<JSONObject>();
        for (Document document : documents) results.add(new JSONObject(document));
        List<Object> collection = doSubGet(mongoTemplate, nosql, parameterMap, results);
        return postGet(nosql, collection);
    }

子查询框架

    private List<Object> doSubGet(MongoTemplate mongoTemplate, JSONObject nosql, Map<String, String> parameterMap, List<JSONObject> results)
            throws Exception {
        List<Object> collection = new ArrayList<Object>();
        for (JSONObject result : results) {
            Boolean checkSub = true;
            for (Object subMap : nosql.getJSONArray("map")) {
                JSONObject subNosql = (JSONObject) (((JSONObject) subMap).clone());
                if (isSimpleType(subNosql.getString("type")))
                    subNosql.put("condition", StringUtility.rebuildQuery(subNosql.getString("condition"), result, "parent__"));
                else subNosql.put("pipeline", StringUtility.rebuildQuery(subNosql.getString("pipeline"), result, "parent__"));
                JSON json = doGet(mongoTemplate, subNosql, parameterMap);
                if (json == null && !(subNosql.getBooleanValue("nullable"))) {
                    checkSub = false;
                    break;
                } else {
                    if (subNosql.getBooleanValue("join")) {
                        result.putAll((JSONObject) json);
                    } else {
                        String subCollectionName = subNosql.getString("collection_name");
                        subCollectionName = subNosql.getBooleanValue("single") ? subCollectionName : subCollectionName + "_list";
                        if (subNosql.containsKey("map_name")) subCollectionName = subNosql.getString("map_name");
                        result.put(subCollectionName, json);
                    }
                }
            }
            if (checkSub) collection.add(result);
        }
        return collection;
    }

查询后处理

    private JSON postGet(JSONObject nosql, List<Object> collection) {
        for (Object object : collection) ((JSONObject) object).remove("_id");
        if (nosql.getBooleanValue("single")) return collection.isEmpty() ? null : (JSONObject) collection.get(0);
        else return new JSONArray(collection);
    }

配置示例

{
    "connection_string":"mongodb://192.168.17.53:27017",
    "database_name":"test",
    "nosql":{
        "collection_name":"computer",
        "condition":"{id > ${id}}",
        "skip":0,
        "limit":2,
        "single":false,
        "nullable":true,
        "sorts":[
            {
                "direction":"asc",
                "name":"price"
            }
        ],
        "map":[
            {
                "collection_name":"screen",
                "condition":"{id == ${parent__screen_id}}",
                "map":[
                    {
                        "collection_name":"gpu",
                        "condition":"{id == ${parent__gpu_id}}",
                        "map":[ ],
                        "single":true,
                        "nullable":true
                    }
                ],
                "single":true,
                "nullable":true
            },
            {
                "collection_name":"mouse",
                "condition":"{id == ${parent__mouse_id}}",
                "map":[ ],
                "single":true,
                "nullable":true
            },
            {
                "collection_name":"os",
                "condition":"{computer_id == ${parent__id}}",
                "map":[
                    {
                        "collection_name":"company",
                        "condition":"{id == ${parent__company_id}}",
                        "map":[ ],
                        "single":true,
                        "nullable":true
                    }
                ],
                "single":false,
                "nullable":true
            },
            {
                "type":"complex",
                "collection_name":"user",
                "map":[ ],
                "pipeline":"[{"$match":{"age":{"$gte":119}}},{"$limit":3}]",
                "single":false,
                "nullable":true
            }
        ]
    }
}

结果示例

{
    "code": 200,
    "data": [
        {
            "mouse": {
                "createTime": "2020-05-13T06:54:38.946+0000",
                "name": "mouse4",
                "weight": 34.0,
                "id": 4.0
            },
            "createTime": "2020-05-13T06:41:45.970+0000",
            "price": 0.0,
            "os_list": [
                {
                    "computer_id": 7.0,
                    "company_id": 1.0,
                    "createTime": "2020-05-13T07:55:52.298+0000",
                    "name": "os3",
                    "company": {
                        "createTime": "2020-05-13T07:59:02.648+0000",
                        "name": "company1",
                        "id": 1.0
                    },
                    "id": 3.0
                }
            ],
            "name": "computer7",
            "screen": {
                "createTime": "2020-05-13T06:46:11.945+0000",
                "name": "screen0",
                "width": 939.0,
                "gpu_id": 1.0,
                "id": 0.0,
                "gpu": {
                    "performance": 87.0,
                    "createTime": "2020-05-13T06:51:25.494+0000",
                    "name": "gpu1",
                    "id": 1.0
                },
                "height": 774.0
            },
            "screen_id": 0.0,
            "id": 7.0,
            "mouse_id": 4.0,
            "user_list": [
                {
                    "id": 1.0,
                    "name": "user1",
                    "age": 119.0,
                    "createTime": "2020-05-08T08:57:18.502+0000"
                },
                {
                    "id": 145.0,
                    "name": "user145",
                    "age": 119.0,
                    "createTime": "2020-05-08T08:57:18.647+0000"
                },
                {
                    "id": 172.0,
                    "name": "user172",
                    "age": 119.0,
                    "createTime": "2020-05-08T08:57:18.683+0000"
                }
            ]
        },
        {
            "mouse": {
                "createTime": "2020-05-13T06:54:38.832+0000",
                "name": "mouse1",
                "weight": 37.0,
                "id": 1.0
            },
            "createTime": "2020-05-13T06:41:45.972+0000",
            "price": 2.0,
            "os_list": [
                {
                    "computer_id": 8.0,
                    "company_id": 2.0,
                    "createTime": "2020-05-13T07:55:52.305+0000",
                    "name": "os8",
                    "company": {
                        "createTime": "2020-05-13T07:59:02.649+0000",
                        "name": "company2",
                        "id": 2.0
                    },
                    "id": 8.0
                }
            ],
            "name": "computer8",
            "screen": {
                "createTime": "2020-05-13T06:46:11.945+0000",
                "name": "screen0",
                "width": 939.0,
                "gpu_id": 1.0,
                "id": 0.0,
                "gpu": {
                    "performance": 87.0,
                    "createTime": "2020-05-13T06:51:25.494+0000",
                    "name": "gpu1",
                    "id": 1.0
                },
                "height": 774.0
            },
            "screen_id": 0.0,
            "id": 8.0,
            "mouse_id": 1.0,
            "user_list": [
                {
                    "id": 1.0,
                    "name": "user1",
                    "age": 119.0,
                    "createTime": "2020-05-08T08:57:18.502+0000"
                },
                {
                    "id": 145.0,
                    "name": "user145",
                    "age": 119.0,
                    "createTime": "2020-05-08T08:57:18.647+0000"
                },
                {
                    "id": 172.0,
                    "name": "user172",
                    "age": 119.0,
                    "createTime": "2020-05-08T08:57:18.683+0000"
                }
            ]
        }
    ]
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值