3

PreparedStatement是否可以在不设置初始 SQL 查询的情况下在 java中创建?

示例代码:

@Override
public List<AccountBean> search(AccountConstraint... c) {
    if (c.length == 0) {
        throw new IllegalArgumentException("dao.AccountDAO.search: c.length == 0");
    }
    try {
        List<AccountBean> beans = new ArrayList<>();
        for (AccountConstraint ac : c) {
            PreparedStatement ps = connection.prepareStatement(null);
            QueryBuilder queryBuilder = new QueryBuilder(ps, "SELECT * FROM accounts");
            queryBuilder.add(ac.getAccountIdConstraint());
            queryBuilder.add(ac.getUsernameConstraint());
            queryBuilder.add(ac.getPasswordConstraint());
            queryBuilder.add(ac.getEmailConstraint());
            //INSERT QUERY INTO PS
            ResultSet rs = ps.executeQuery();
            while (rs.next()) {
                beans.add(new AccountBean(rs));
            }
        }
        return beans;
    } catch (SQLException ex) {
        throw new RuntimeException(ex);
    }
}

诀窍在于QueryBuilder,此类负责根据初始 SELECT 部分构建查询的部分,然后添加相应的 WHERE 和 AND 子句。

然而,为了确保所有数据都是安全的,实际的参数也必须放在 PreparedStatement 中,这就是为什么它被传递给 QueryBuilder。

EveryQueryBuilder.add()将一些参数添加到 PreparedStatement 中,并将特定字符串附加到查询的末尾。

我认为一些变通方法是可能的,例如不给 aPreparedStatementQueryBuilder会给 aList<Object>然后你会写一个自定义函数把它们放在PreparedStatement后面。

但是您对此有何想法和建议?

问候。

添加了解决方案

首先有几个关键变化:

  • QueryBuilder现在正确地实现了 Builder 模式。
  • QueryBuilder.add()一次接受多个Constraints。
  • AccountConstraint现在可以给出一个给出所有Constraints 的数组。

@Override
public List<AccountBean> search(AccountConstraint... c) {
    if (c.length == 0) {
        throw new IllegalArgumentException("dao.AccountDAO.search: c.length == 0");
    }
    try {
        List<AccountBean> beans = new ArrayList<>();
        for (AccountConstraint ac : c) {
            try (PreparedStatement ps = new QueryBuilder("SELECT * FROM accounts").add(ac.getConstraints()).build();ResultSet rs = ps.executeQuery()) {
                while (rs.next()) {
                    beans.add(new AccountBean(rs));
                }
            }
        }
        return beans;
    } catch (SQLException ex) {
        throw new RuntimeException(ex);
    }
}

附言。try{ }由于 try-with-resources,我将两个语句合二为一。

4

3 回答 3

5

准备语句意味着编译它,以便您可以使用不同的参数有效地多次执行它。所以,不,在定义之前编译查询是没有意义的。

据我了解,您希望使用 Java 编译器来帮助您动态定义查询。您为什么不在compile()方法中创建准备好的语句,因此,作为您的构建器的结果。add()此外,如果您使用构建器模式以便每次调用都返回,您的代码将变得更具可读性并且更像声明性查询this。然后你可以这样写你的查询:

PreparedStatement ps = new QueryBuilder()
   .select("*")
   .from("accounts")
   .where()
   .add(yourConstraint())
   ...
   .compile();

但是,您必须在循环之前创建准备好的语句。否则,如果您保留对构建器的引用并compile()在循环中调用,您将在每次调用时获得一个新的准备好的语句。因此,您不会获得重用预编译查询的好处。在循环中,您只需为准备好的语句中的变量赋值。

于 2013-06-29T12:35:41.723 回答
1

创建后,您无法通过 API 修改准备好的语句。如果没有 SQL 语句,您也无法创建它。

为什么不单独创建查询然后绑定参数呢?您可以使用 Map 来保存参数占位符及其值,以便将它们设置为准备好的语句。

虽然我只是使用 Spring 的 JDBC 模板来更快地完成相同的事情。

于 2013-06-29T12:31:29.477 回答
0

如何改进您的 SQL 查询构建器

如果您看看像 jOOQ其他人这样流行的查询构建器是如何做到的,那么您的想法是您可以更彻底地分离您的关注点。你应该有:

  • SQL 语句的表达式树表示(理想情况下不直接对字符串进行操作)
  • 一种方便地构造表达式树的方法,例如使用 DSL
  • 某种生成 SQL 字符串、准备语句、绑定变量等的执行生命周期管理。

或者在代码中(jOOQ 示例,但这也适用于您自己的查询构建器):

Result<?> result =

// This constructs the expression tree through the jOOQ DSL
ctx.selectFrom(ACCOUNTS)
   .where(ac.getAccountIdConstraint())
   .and(ac.getUsernameConstraint())
   .and(ac.getPasswordConstraint())
   .and(ac.getEmailConstraint())

// This internally creates a PreparedStatement, binds variables, executes it, and maps results
   .fetch();

当然,您的AccountConstraint.getXYZConstraint()方法不会返回 SQL 字符串片段,而是返回表达式树元素。在 jOOQ 的情况下,这将是一个Condition

(免责声明:我为 jOOQ 的供应商工作)

如何提高 SQL 性能

我注意到您对 NAccountConstraint个值运行 N 次查询,并且您以某种方式混合结果,即哪个AccountConstraint值产生哪个AccountBean. 我强烈建议您将该循环移到生成的 SQL 查询中,因为您将在几乎每个数据库上获得更快的结果。我在这里写过关于这个的博客

于 2018-11-22T15:40:03.640 回答