最佳实践 - 基于用户选择构建查询

Posted

技术标签:

【中文标题】最佳实践 - 基于用户选择构建查询【英文标题】:Best Practice - Building a query based on user selections 【发布时间】:2012-10-25 22:11:28 【问题描述】:

我正在创建一个基于 Web 的小型工具,允许用户以各种方式查询大型查找表。由于它是一个小工具,我使用的是 JSP/Servlets。

查找表有一个类似这样的定义:

第 1 列 |第 2 列 |第 3 列 |日期 |用户 |数

用户可以按列值或范围查询。此外,结果可以按特定列排序。因为该表有几十万条记录,并且增长迅速,所以我使用 Oracle 的 ROWNUM 并且只返回一小部分结果。

我有一个从用户那里获取搜索条件的表单页面。我根据一系列条件生成查询,例如:

query = "SELECT * FROM mytable WHERE 1=1 "
if(searchCriteria1 != "")
    query += "AND column1='searchCriteria1' "
if(searchCriteria2 != "")
    query += "AND column2='searchCriteria2' "
if(searchCriteria2 != "")
    query += "AND column2='searchCriteria2' "
if(searchCriteria3 != "")
    query += "AND column3='searchCriteria3' "
if((searchCriteria4 != "") && (searchCriteria5 != ""))
    query += "AND date>='searchCriteria4' AND date<='searchCriteria5' "
etc...

(这只是简化的伪代码)

在显示第一个结果页面后处理排序。用户单击该页面的列标题以按该列排序。这将是回发并查询数据库。基本上,我在上面运行相同的代码,但最后是这样的:

if(sortColumn1)
    query += "ORDER BY column1"
if(sortColumn2)
    query += "ORDER BY column2"
if(sortColumn3)
    query += "ORDER BY column3"

因此,正如您想象的那样,我的查询构建代码非常长,包含所有这些不同的条件。有什么更好的方法建议吗?

【问题讨论】:

为什么不能在客户端使用 jquery 进行排序?或任何其他 js 插件 首先,我认为您不需要返回符合条件的所有内容,您只需返回前100名甚至更少。其次,是否可以将大查找表拆分为几个小表,因为我认为对于某些列,它们被搜索的机会比其他列高得多,通过将它们拆分并稍后在适当的时间将它们重新连接在一起可以提高性能。第三,我认为您不必在服务器端进行所有排序。 【参考方案1】:

字符串连接不是 SQL 的最佳选择。您可以做的最好的事情是使用QueryDSL 或JOOQ 并以OO 方式进行。我对 QueryDSL 比较熟悉。看例子here。

SQLQuery query = new SQLQueryImpl(connection, dialect); 
query.from(myTable);

BooleanBuilder wheres = new BooleanBuilder();

if(notBlank(searchCriteria1))
   wheres.and(myTable.column1.eq(searchCriteria1));
if(notBlank(searchCriteria2))
   wheres.and(myTable.column2.eq(searchCriteria2));
if(notBlank(searchCriteria4) && notBlank(searchCriteria5))
   wheres.andAllOf(myTable.date.goe(searchCriteria4),  myTable.date.loe(searchCriteria5));  //you may want to use myTable.date.between(...)

if (...) 
   query.orderBy(myTable.column1.asc());
 else if (...)
   query.orderBy(myTable.column2.asc());


query.limit(100); //it is good to limit a result
query.list(myTable.all());

至于 SQL 注入查询引擎将在构造 SQL 时将您的参数包装在命名参数中。

【讨论】:

【参考方案2】:

首先,不要使用简单的String 进行查询。您应该使用PreparedStatement,它可以保护您免受SQL injection(恶意用户破坏易受攻击系统的一种非常简单的方法)。

其次,如果您的输入确实如此复杂,您将无法避免if()... else if()... 块。看起来你的方法做得比它应该做的要多得多。考虑根据用户想要运行的查询类型调用不同的方法。我已经实现了许多 DAO,在将 AND 行添加到 WHERE 子句时,无法绕过 if 块中的所有这些空检查。

不过,您绝对可以做的一件事是将大部分查询写在 private static final String 常量中,并在您希望参数所在的位置(?)带有问号,然后根据用户附加您需要的其他条件输入,然后直接从中构造您的PreparedStatement。一旦你创建了PreparedStatement,你就可以使用各种setter 命令来填充那些?s。这对代码进行了相当多的整理。

【讨论】:

【参考方案3】:

永远不要使用字符串连接将用户输入的数据放入您的查询中。您应该在查询中使用命名参数或位置参数并传递参数。

【讨论】:

是的。我曾经被注入过一次Sql,这是一种耻辱。【参考方案4】:

我认为向后端发送请求以进行排序不是一个好主意。 js中有很多插件可以对表格进行排序,可以使用。

【讨论】:

使用javascript对几十万行进行排序?他将不得不对排序结果进行回调。 不,我的意思是,一旦他得到一些结果,就不需要进一步向后端发出请求,以按该组结果中的不同列排序。还是我错过了什么? 如果他返回的结果数是 10,000 条长怎么办?将整个结果集发送回客户端上的表并依靠 javascript 对它们进行排序是非常糟糕的。他将需要分页结果,这意味着每个新页面都需要往返服务器,如果排序字段发生更改,则需要往返服务器以获取新页面。【参考方案5】:

从简单的角度来看

如果您知道所有类型的列,则可以使用数组和表来使此代码更受数据驱动。

【讨论】:

以上是关于最佳实践 - 基于用户选择构建查询的主要内容,如果未能解决你的问题,请参考以下文章

在微服务架构下基于 Prometheus 构建一体化监控平台的最佳实践

在微服务架构下基于 Prometheus 构建一体化监控平台的最佳实践

构建基于 Actor 的系统的设计模式/最佳实践

平均堆栈身份验证 - 最佳实践

在微服务架构下基于 Prometheus 构建一体化监控平台的最佳实践

Mysql 索引:查询生成器的最佳实践