如何在带有jooq的普通sql中使用命名参数

Posted

技术标签:

【中文标题】如何在带有jooq的普通sql中使用命名参数【英文标题】:How to use named parameter in plain sql with jooq 【发布时间】: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();

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

【问题讨论】:

【参考方案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 *** 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://***.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,
        )
    



【讨论】:

以上是关于如何在带有jooq的普通sql中使用命名参数的主要内容,如果未能解决你的问题,请参考以下文章

如何将 MySQL JOOQ 重命名表查询范围限定为同一个数据库?

模板函数与带有自动参数的命名 lambda

如何使用 JDBC 调用带有命名参数的 Sybase 存储过程

如何从 SQL 查询配置 jOOQ 可嵌入类型

默认形参和关键字实参,收集参数,命名关键字参数,return自定义返回,全局变量和局部变量,函数名的使用

默认形参和关键字实参,收集参数,命名关键字参数,return自定义返回,全局变量和局部变量,函数名的使用