原文はこちら。
The original article was written by Dustin Marx.
http://marxsoftware.blogspot.com/2020/12/jdk11-string-repeat-preparedstatement.html
JavaのString.repeat(int)
メソッドは、(JDK 11で導入された)Javaの小さな追加機能の一例ですが、多用しており、感謝しています。この記事では、JDK 11で導入されたString.repeat(int)
を使って、PreparedStatements
で使用する適切な数の”? “パラメータ・プレースホルダを持つSQLのWHERE
句を簡単にカスタム生成する方法について説明します。
Class String repeat(int)
https://docs.oracle.com/javase/jp/15/docs/api/java.base/java/lang/String.html#repeat%28int%29
https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/lang/String.html#repeat%28int%29
Applying New JDK 11 String Methods
https://marxsoftware.blogspot.com/2018/07/new-jdk-11-string-methods.html
Using Prepared Statements
https://docs.oracle.com/javase/tutorial/jdbc/basics/prepared.html
Interface PreparedStatement
https://docs.oracle.com/javase/jp/15/docs/api/java.sql/java/sql/PreparedStatement.html
https://docs.oracle.com/en/java/javase/15/docs/api/java.sql/java/sql/PreparedStatement.html
多くのJava開発者は、適切な数のパラメータ・プレースホルダを持つPreparedStatements
を手動で作成する必要はありません。JPAの実装や他のORMフレームワーク、ライブラリを利用しているからです。しかし、この記事のデモでは、String.repeat(int)
を使って、指定された数の繰り返し部分を持つ文字列を構築する必要があるあらゆる実装を簡単に行うことができることを示しています。
Basic Java Persistence API Best Practices
https://www.oracle.com/technical-resources/articles/javaee/marx-jpa.html
Using prepared statements with JDBCTemplate
https://stackoverflow.com/questions/2989245/using-prepared-statements-with-jdbctemplate/2990103#2990103
Building SQL IN
Condition with Dynamic Number of Parameters
潜在的な値のコレクションに対して特定のデータベースカラムを問い合わせるカスタムのSELECT
文を構築するためにJavaアプリケーションで使用される一般的なアプローチは、IN
演算子を使用し、すべての潜在的な一致する値をIN
演算子に渡すことです。
SELECT
文のWHERE
句のIN
演算子部分を構築するための1つのJava実装方法は、IN
演算子のパラメータの数と同じ回数を繰り返し、そのループ内で条件式を使用して、進行中のIN
部分をどのように適切に追加するかを判断することです。以下のコードで説明します。
/**
* Demonstrates "traditional" approach for building up the
* "IN" portion of a SQL statement with multiple parameters
* that uses a conditional within a loop on the number of
* parameters to determine how to best handle each.
*
* @param columnName Name of database column to be referenced
* in the "IN" clause.
* @param numberPlaceholders Number of parameters for which
* placeholder question marks ("?") need to be added.
* @return The "IN" portion of a SQL statement with the
* appropriate number of placeholder question marks.
*/
public String generateInClauseTraditionallyOne(
final String columnName, final int numberPlaceholders)
{
final StringBuilder inClause = new StringBuilder();
inClause.append(columnName + " IN (");
for (int placeholderIndex = 0; placeholderIndex < numberPlaceholders; placeholderIndex++)
{
if (placeholderIndex != numberPlaceholders-1)
{
inClause.append("?, ");
}
else
{
inClause.append("?");
}
}
inClause.append(")");
return inClause.toString();
}
IN
句を構成して動的な個数のパラメータのプレースホルダーを使う2個目の伝統的な方法は、パラメータの個数と同じ回数だけ再びループを行い、各ループにおいて全く同じ新しいテキストを追加する、というものです。反復処理が終わると、余分な文字を最後に切り落とします。この方法を以下のコードで説明しています。
/**
* Demonstrates "traditional" approach for building up the
* "IN" portion of a SQL statement with multiple parameters
* that treats each looped-over parameter index the same and
* the removes the extraneous syntax from the end of the
* generated string.
*
* @param columnName Name of database column to be referenced
* in the "IN" clause.
* @param numberPlaceholders Number of parameters for which
* placeholder question marks ("?") need to be added.
* @return The "IN" portion of a SQL statement with the
* appropriate number of placeholder question marks.
*/
public String generateInClauseTraditionallyTwo(
final String columnName, final int numberPlaceholders)
{
final StringBuilder inClause = new StringBuilder();
inClause.append(columnName + " IN (");
for (int placeholderIndex = 0; placeholderIndex < numberPlaceholders; placeholderIndex++)
{
inClause.append("?, ");
}
inClause.delete(inClause.length()-2, inClause.length());
inClause.append(")");
return inClause.toString();
}
JDK 11では、String.repeat(int)
を含む一連の便利な新しいString
クラスのメソッドが導入されました。String.repeat(int)
メソッドは、以下のコードで示すように、動的な数のパラメータ・プレースホルダーを持つカスタムIN
演算子を生成するためのこれらのアプローチを1行にまとめています。
/**
* Demonstrates JDK 11 {@link String#repeat(int)} approach
* for building up the "IN" portion of a SQL statement with
* multiple parameters.
*
* @param columnName Name of database column to be referenced
* in the "IN" clause.
* @param numberPlaceholders Number of parameters for which
* placeholder question marks ("?") need to be added.
* @return The "IN" portion of a SQL statement with the
* appropriate number of placeholder question marks.
*/
public String generateInClauseWithStringRepeat(
final String columnName, final int numberPlaceholders)
{
return columnName + " IN (" + "?, ".repeat(numberPlaceholders-1) + "?)";
}
String.repeat(int)
を使用すれば、1行で目的を達成でき、明示的なループやStringBuilder
の明示的なインスタンス化は必要ありません。
Building SQL OR
Conditions with Dynamic Number of Parameters
複数の値に対してテストを行うために、IN
の代わりに複数のSQLのOR
条件を使用できます。例えば、パラメータの数が1000を超えていて、IN
が1000要素までしかサポートしていないOracle Databaseを使用している場合、これは必須です。
Does PostgreSQL Have an ORA-01795-like Limit?
http://marxsoftware.blogspot.com/2015/11/does-postgresql-have-ora-01795.html
IN
条件の使用と同様に、動的な数のパラメータプレースホルダーに対するOR
条件を構築するためによく使用される2つのアプローチは、各エントリの出力が書き込まれた通りに正しく書き込まれているかどうかをチェックする条件でループするか、ループ後に余計な文字を削除する、というものです。この2つのアプローチを以下のコードで示します。
/**
* Demonstrates "traditional" approach for building up the
* "OR" portions of a SQL statement with multiple parameters
* that uses a conditional within a loop on the number of
* parameters to determine how to best handle each.
*
* @param columnName Name of database column to be referenced
* in the "OR" clauses.
* @param numberPlaceholders Number of parameters for which
* placeholder question marks ("?") need to be added.
* @return The "OR" portions of a SQL statement with the
* appropriate number of placeholder question marks.
*/
public String generateOrClausesTraditionallyOne(
final String columnName, final int numberPlaceholders)
{
final StringBuilder orClauses = new StringBuilder();
for (int placeholderIndex = 0; placeholderIndex < numberPlaceholders; placeholderIndex++)
{
if (placeholderIndex != numberPlaceholders-1)
{
orClauses.append(columnName).append(" = ? OR ");
}
else
{
orClauses.append(columnName).append(" = ?");
}
}
return orClauses.toString();
}
/**
* Demonstrates "traditional" approach for building up the
* "OR" portions of a SQL statement with multiple parameters
* that treats each looped-over parameter index the same and
* the removes the extraneous syntax from the end of the
* generated string.
*
* @param columnName Name of database column to be referenced
* in the "OR" clauses.
* @param numberPlaceholders Number of parameters for which
* placeholder question marks ("?") need to be added.
* @return The "OR" portions of a SQL statement with the
* appropriate number of placeholder question marks.
*/
public String generateOrClausesTraditionallyTwo(
final String columnName, final int numberPlaceholders)
{
final StringBuilder orClauses = new StringBuilder();
for (int placeholderIndex = 0; placeholderIndex < numberPlaceholders; placeholderIndex++)
{
orClauses.append(columnName + " = ? OR ");
}
orClauses.delete(orClauses.length()-4, orClauses.length());
return orClauses.toString();
}
ここでもString.repeat(int)
を使うと簡単です。
/**
* Demonstrates JDK 11 {@link String#repeat(int)} approach
* for building up the "OR" portions of a SQL statement with
* multiple parameters.
*
* @param columnName Name of database column to be referenced
* in the "OR" clauses.
* @param numberPlaceholders Number of parameters for which
* placeholder question marks ("?") need to be added.
* @return The "OR" portions of a SQL statement with the
* appropriate number of placeholder question marks.
*/
public String generateOrClausesWithStringRepeat(
final String columnName, final int numberPlaceholders)
{
final String orPiece = columnName + " = ? OR ";
return orPiece.repeat(numberPlaceholders-1) + columnName + " = ?";
}
Conclusion
String.repeat(int)
の導入により、Java開発者が動的に繰り返される部分から構成されるJava文字列のカスタム生成を容易に実装できるようになりました。
このエントリで紹介した全てのコードスニペットはGitHubからご利用いただけます。
DynamicPreparedStatementParameters.java
https://github.com/dustinmarx/javademos/blob/master/src/dustin/examples/jdk11/strings/DynamicPreparedStatementParameters.java