【问题标题】:How to use named parameter in plain sql with jooq如何在带有jooq的普通sql中使用命名参数
【发布时间】:2013-04-29 22:28:42
【问题描述】:

我正在使用带有普通/原始 SQL 的 JOOQ,这意味着我没有使用任何代码生成或流体 DSL 的东西。

以下代码有效:

Connection connection = ...;
DSLContext context = DSL.using(connection, ...);
String sql = "select * from mytable t where (t.id = ?)"; 
String id = ...; //
Result<Record> result = context.fetch(sql, id);

现在假设我有一个带有多个参数的查询,如下所示:

String sql = "select * from mytable t where (t.id = ?) " + 
             "and (t.is_active = ?) and (t.total > ?)"; 

如何在这些类型的查询中使用命名参数? 我在想类似的东西:

String sql = "select * from mytable t where (t.id = :id) " + 
             "and (t.is_active = :is_active) and (t.total > :total)"; 

ResultQuery<Record> rq = context.resultQuery(sql);
rq.getParam("id").setValue(...); 
rq.getParam("is_active").setValue(...);
rq.getParam("total").setValue(...);
Result<Record> result = rq.fetch();

但是上面的代码不起作用(原因很明显)。提前致谢。

【问题讨论】:

    标签: java sql jooq


    【解决方案1】:

    jOOQ 目前不支持使用命名参数执行 SQL。如果您使用另一个 API(例如 Spring JDBC)执行查询,则可以使用 jOOQ 呈现命名参数。有关更多信息,请参阅手册:

    http://www.jooq.org/doc/latest/manual/sql-building/bind-values/named-parameters

    但plain SQL templating API 允许重复使用模板,例如

    String sql = "select * "
               + "from mytable t "
               + "where t.id = {0} or (t.id != {0} and t.name = {1})";
    ResultQuery<Record> q = ctx.resultQuery(sql, val(1), val("A"));
    

    这样,您至少可以多次重复使用值。

    【讨论】:

    • 好的,我接受答案,但让我重新表述一下问题:假设我有一个带有多个参数的 SQL 语句,并且 jooq 不支持命名参数,推荐的解决方法是什么?手动替换字符串很麻烦...
    • 一种解决方法是使用索引参数,当然......我认为目前没有一种优雅的方法可以做到这一点,缺少索引参数或使用手动字符串替换.. .
    • 嗨,@LukasEder,今天还不支持吗?
    • @a.l.:不,不是。相关的功能请求是这样的:github.com/jOOQ/jOOQ/issues/7014。我已经用另一种方法更新了我的答案,它为绑定值多次重复使用的用例提供了解决方案。
    【解决方案2】:

    因为 Lukas 说这个功能不可用,所以我想我会编写一个“足够好”的解决方案。

    这个想法是像:name这样的变量名必须在所有地方都替换为{0},其余的由JOOQ完成。我认为这是最简单的方法。 (用正确的形式替换变量,比如处理数据类型肯定是很多工作。)

    我从 this other StackOverflow answer 和 created this gist in Kotlin 那里得到了一些想法(否则在 Java 中会太长)。

    当前的要点现在看起来像这样:

    
    import org.jooq.DSLContext
    import org.jooq.Record
    import org.jooq.ResultQuery
    import org.jooq.impl.DSL
    
    object SqlJooqBindVariableOrganizer {
    
        data class Processed(
            val statement: String,
            val originalStatement: String,
            val variables: List<Pair<String, Any>>,
        ) {
    
            fun toResultQuery(context: DSLContext): ResultQuery<Record> {
                return context.resultQuery(
                    statement,
                    *variables.map { DSL.`val`(it.second) }.toTypedArray(),
                )
            }
    
        }
    
        private fun extractBindVariableLocations(
            statement: String,
        ): Map<String, List<IntRange>> {
            // https://stackoverflow.com/a/20644736/4420543
            // https://gist.github.com/ruseel/e10bd3fee3c2b165044317f5378c7446
            // not sure about this regex, I haven't used colon inside string to test it out
            return Regex("(?<!')(:[\\w]*)(?!')")
                .findAll(statement)
                .map { result ->
                    val variableName = result.value.substringAfter(":")
                    val range = result.range
                    variableName to range
                }
                .groupBy(
                    { it.first },
                    { it.second }
                )
        }
    
        fun createStatement(
            statement: String,
            vararg variables: Pair<String, Any>,
        ): Processed {
            return createStatement(statement, variables.toList())
        }
    
        fun createStatement(
            statement: String,
            variables: List<Pair<String, Any>>,
        ): Processed {
            val locations = extractBindVariableLocations(statement)
    
            val notProvidedKeys = locations.keys.subtract(variables.map { it.first })
            if (notProvidedKeys.isNotEmpty()) {
                throw RuntimeException("Some variables are not provided:\n"
                        + notProvidedKeys.joinToString()
                )
            }
    
            val relevantVariables = variables
                // there may be more variables provided, so filter this
                .filter { it.first in locations.keys }
    
            // these locations should have the same order as the variables
            // so it is important to know the proper order of the indices
            val variableNameToIndex = relevantVariables
                .mapIndexed { index, variable -> variable.first to index }
                .associateBy({ it.first }, { it.second })
    
    
            val variableNameReplacements = locations
                .flatMap { (variableName, ranges) ->
                    ranges.map { range -> variableName to range }
                }
                // the replacements have to be done in a reversed order,
                // as the replaced string is not equal length
                .sortedByDescending { it.second.first }
    
            // replace :name with {0}
            val processedStatement = variableNameReplacements
                .fold(statement) { statementSoFar, (variableName, range) ->
                    // index has to exist, we just checked it
                    val index = variableNameToIndex[variableName]!!
    
                    statementSoFar.replaceRange(range, "{$index}")
                }
    
            return Processed(
                statement = processedStatement,
                originalStatement = statement,
                variables = relevantVariables,
            )
        }
    
    }
    
    

    【讨论】:

      猜你喜欢
      • 2016-10-08
      • 1970-01-01
      • 1970-01-01
      • 2019-07-04
      • 1970-01-01
      • 2020-02-21
      • 2011-10-23
      • 2016-04-27
      • 2021-04-21
      相关资源
      最近更新 更多