2014-12-05 72 views
1

我在Spring中为项目创建REST API。春季地图可选查询参数到SQL准备语句

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

例如,我有一个产品的收集和我有很多的查询参数

/账户?的categoryId =不便&为了= ASC &价格= 上限= 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

我发现上面的代码很难维护,因为它有复杂的逻辑来构建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)"; 
    } 
+0

这种方法的另一个问题是,我失去的参数类型安全。作为收集资源,我必须分页。每次查询参数映射时,我都必须检查它是否为LIMIT或OFFSET,并将参数值转换为long。我必须为每个允许的参数附加一组规则。我是否让这个过于复杂? – 2014-12-06 07:57:35

+1

是的,我认为你让它变得复杂:)。您原来的方法中也丢失了类型安全性,主要是因为您收到一个Map 作为参数。为此,您可以尝试@QueryParam,但namedParameters贴图仍然必须是Map 。如果你想使地图类型安全,那么在编写适用于所有参数的通用代码时就会失败。 – 2014-12-06 10:39:37

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()); 
}