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 の実行をサポートしていません。Spring JDBC などの別の API でクエリを実行している場合は、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}あり、残りは 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 に答える