5

我正在使用带有普通/原始 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();

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

4

2 回答 2

6

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

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

但是普通的 SQL 模板 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"));

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

于 2013-05-06T19:58:57.197 回答
0

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

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

我从其他 StackOverflow 答案中获得了一些想法,然后在 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,
        )
    }

}

于 2021-11-13T20:44:17.530 回答