我在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());
}
我可以改善吗?或者有更好的方法吗?
答案 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());
}