Java’s String.repeat Method in Action: Building PreparedStatement with Dynamic Number of Parameters

原文はこちら。
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

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

%s と連携中