通過配置的方式實現類似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"
                }
            ]
        }
    ]
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章