如何在带有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 重命名表查询范围限定为同一个数据库?
如何使用 JDBC 调用带有命名参数的 Sybase 存储过程