spring map可选查询参数,以sql编写语句

时间:2014-12-05 20:29:24

标签: sql spring jdbc parameters

我在Spring中为项目创建REST API。

我面临的问题是如何优雅地创建具有可变数量参数的PreparedStatement。

例如。我有一个产品系列,我有很多查询参数

/帐户的categoryId =不便&安培;为了= ASC&安培;价格= LT; 1000&安培;限制= 10安培;偏移量= 300

问题是这些参数可能设置也可能不设置。

目前我有一些看起来像这样的东西,但我还没有开始消毒用户输入

控制器

@RequestMapping(method = RequestMethod.GET)
public List<Address> getAll(@RequestParam Map<String, String> parameters) {
        return addressRepository.getAll(parameters);
}

存储库

@Override
public List<Address> getAll(Map<String, String> parameters) {
    StringBuilder conditions = new StringBuilder();
    List<Object> parameterValues = new ArrayList<Object>();
    for(String key : parameters.keySet()) {
        if(allowedParameters.containsKey(key) && !key.equals("limit") && !key.equals("offset")) {
            conditions.append(allowedParameters.get(key));
            parameterValues.add(parameters.get(key));
        }
    }
    int limit = Pagination.DEFAULT_LIMIT_INT;
    int offset = Pagination.DEFAULT_OFFSET_INT;
    if(parameters.containsKey("limit"))
        limit = Pagination.sanitizeLimit(Integer.parseInt(parameters.get("limit")));
    if(parameters.containsKey("offset"))
        offset = Pagination.sanitizeOffset(Integer.parseInt(parameters.get("offset")));
    if(conditions.length() != 0) {
        conditions.insert(0, "WHERE ");
        int index = conditions.indexOf("? ");
        int lastIndex = conditions.lastIndexOf("? ");
        while(index != lastIndex) {
            conditions.insert(index + 2, "AND ");
            index = conditions.indexOf("? ", index + 1);
            lastIndex = conditions.lastIndexOf("? ");
        }
    }
    parameterValues.add(limit);
    parameterValues.add(offset);
    String base = "SELECT * FROM ADDRESSES INNER JOIN (SELECT ID FROM ADDRESSES " + conditions.toString() + "LIMIT ? OFFSET ?) AS RESULTS USING (ID)";
    System.out.println(base);
    return jdbc.query(base, parameterValues.toArray(), new AddressRowMapper());
}

我可以改善吗?或者有更好的方法吗?

2 个答案:

答案 0 :(得分:2)

我发现上面的代码难以维护,因为它具有构建where子句的复杂逻辑。 Spring的NamedParameterJdbcTemplate可用于简化逻辑。关注this链接,了解NamedParameterJdbcTemplate上的基本示例

以下是新代码的外观:

    public List<Address> getAll(Map<String, String> parameters) {
        Map<String, Object> namedParameters = new HashMap<>();
        for(String key : parameters.keySet()) {
            if(allowedParameters.contains(key)) {
                namedParameters.put(key, parameters.get(key));
            }
        }

        String sqlQuery = buildQuery(namedParameters);

        NamedParameterJdbcTemplate template = new NamedParameterJdbcTemplate(null /* your data source object */);
        return template.query(sqlQuery, namedParameters, new AddressRowMapper());
    }

    private String buildQuery(Map<String, Object> namedParameters) {
        String selectQuery = "SELECT * FROM ADDRESSES INNER JOIN (SELECT ID FROM ADDRESSES ";
        if(!(namedParameters.isEmpty())) {
            String whereClause = "WHERE ";
            for (Map.Entry<String, Object> param : namedParameters.entrySet()) {
                whereClause += param.getKey() + " = :" + param.getValue();
            }

            selectQuery += whereClause;
        }
        return selectQuery + " ) AS RESULTS USING (ID)";
    }

答案 1 :(得分:0)

经过一番思考后,我认为类型安全很重要,我决定在整个API中使用以下样式。

@RequestMapping(method = RequestMethod.GET)
public List<Address> getAll(@RequestParam(value = "cityId", required = false) Long cityId,
                            @RequestParam(value = "accountId", required = false) Long accountId,
                            @RequestParam(value = "zipCode", required = false) String zipCode,
                            @RequestParam(value = "limit", defaultValue = Pagination.DEFAULT_LIMIT_STRING) Integer limit,
                            @RequestParam(value = "offset", defaultValue = Pagination.DEFAULT_OFFSET_STRING) Integer offset) {
    Map<String, Object> sanitizedParameters = AddressParameterSanitizer.sanitize(accountId, cityId, zipCode, limit, offset);
    return addressRepository.getAll(sanitizedParameters);
}

参数卫生

public static Map<String, Object> sanitize(Long accountId, Long cityId, String zipCode, Integer limit, Integer offset) {
    Map<String, Object> sanitizedParameters = new LinkedHashMap<String, Object>(5);

    if(accountId != null) {
        if (accountId < 1) throw new InvalidQueryParameterValueException(ExceptionMessage.INVALID_ID);
        else sanitizedParameters.put("ACCOUNT_ID = ? ", accountId);
    }

    if(cityId != null) {
        if (cityId < 1) throw new InvalidQueryParameterValueException(ExceptionMessage.INVALID_ID);
        else sanitizedParameters.put("CITY_ID = ? ", cityId);
    }

    if(zipCode != null) {
        if (!zipCode.matches("[0-9]+")) throw new InvalidQueryParameterValueException(ExceptionMessage.INVALID_ZIP_CODE);
        else sanitizedParameters.put("ZIP_CODE = ? ", zipCode);
    }

    if (limit < 1 || limit > Pagination.MAX_LIMIT_INT) throw new InvalidQueryParameterValueException(ExceptionMessage.INVALID_LIMIT);
    else sanitizedParameters.put("LIMIT ? ", Pagination.sanitizeLimit(limit));

    if(offset < 0) throw new InvalidQueryParameterValueException(ExceptionMessage.INVALID_OFFSET);
    else sanitizedParameters.put("OFFSET ?", Pagination.sanitizeOffset(offset));

    return sanitizedParameters;
}

SQL查询字符串构建器

public static String buildQuery(Tables table, Map<String, Object> sanitizedParameters) {
    String tableName = table.name();
    String baseQuery = "SELECT * FROM " + tableName + " INNER JOIN (SELECT ID FROM " + tableName;
    String whereClause = " ";
    if(sanitizedParameters.size() > 2) {
        whereClause += "WHERE ";
    }
    if(!sanitizedParameters.isEmpty()) {
        for(String key : sanitizedParameters.keySet()) {
            whereClause += key;
        }
        baseQuery += whereClause;
    }
    return baseQuery + ") AS RESULTS USING (ID)";
}

存储库:

@Override
public List<Address> getAll(Map<String, Object> sanitizedParameters) {
    String sqlQuery = CollectionQueryBuilder.buildQuery(Tables.ADDRESSES, sanitizedParameters);
    return jdbc.query(sqlQuery, sanitizedParameters.values().toArray(), new AddressRowMapper());
}