web-dev-qa-db-ja.com

IN句のパラメータ化にJDBCを使用する最良の方法は何ですか?

フォームのクエリがあるとします

SELECT * FROM MYTABLE WHERE MYCOL in (?)

そして、引数をinにパラメーター化したいと思います。

JDBCでJavaでこれを行う簡単な方法はありますか。SQL自体を変更せずに複数のデータベースで機能しますか?

最も近い 私が見つけた質問はC#に関係していました 、Java/JDBCに何か違うものがあるかどうか疑問に思っています。

37
Uri

実際、JDBCでこれを行う簡単な方法はありません。 一部JDBCドライバは、IN句で PreparedStatement#setArray() をサポートしているようです。どれがどれなのかわからないだけです。

String#join() および Collections#nCopies() でヘルパーメソッドを使用して、IN句のプレースホルダーを生成し、 PreparedStatement#setObject() でループ内のすべての値を設定する別のヘルパーメソッド。

public static String preparePlaceHolders(int length) {
    return String.join(",", Collections.nCopies(length, "?"));
}

public static void setValues(PreparedStatement preparedStatement, Object... values) throws SQLException {
    for (int i = 0; i < values.length; i++) {
        preparedStatement.setObject(i + 1, values[i]);
    }
}

使用方法は次のとおりです。

private static final String SQL_FIND = "SELECT id, name, value FROM entity WHERE id IN (%s)";

public List<Entity> find(Set<Long> ids) throws SQLException {
    List<Entity> entities = new ArrayList<Entity>();
    String sql = String.format(SQL_FIND, preparePlaceHolders(ids.size()));

    try (
        Connection connection = dataSource.getConnection();
        PreparedStatement statement = connection.prepareStatement(sql);
    ) {
        setValues(statement, ids.toArray());

        try (ResultSet resultSet = statement.executeQuery()) {
            while (resultSet.next()) {
                entities.add(map(resultSet));
            }
        }
    }

    return entities;
}

private static Entity map(ResultSet resultSet) throws SQLException {
    Enitity entity = new Entity();
    entity.setId(resultSet.getLong("id"));
    entity.setName(resultSet.getString("name"));
    entity.setValue(resultSet.getInt("value"));
    return entity;
}

一部のデータベースでは、IN句に許容される値の量に制限があることに注意してください。たとえば、Oracleでは1000アイテムに対してこの制限があります。

43
BalusC

ラージIN句(100以上)のケースに誰も答えないので、JDBCにうまく機能するこの問題の解決策を投げます。要するに、tmpテーブルでININNER JOINに置き換えます。

私がやることは、バッチIDテーブルと呼ばれるものを作成することであり、RDBMSに応じて、それをtmpテーブルまたはメモリテーブルに作成することができます。

テーブルには2つの列があります。 IN句からのIDを持つ1つの列と、その場で生成するバッチIDを持つ別の列。

SELECT * FROM MYTABLE M INNER JOIN IDTABLE T ON T.MYCOL = M.MYCOL WHERE T.BATCH = ?

選択する前に、指定されたバッチIDでIDをテーブルに押し込みます。次に、元のクエリのIN句を、IDテーブルで一致するINNER JOINに置き換えるだけで、WHERE batch_idは現在のバッチに等しくなります。完了したら、バッチのエントリを削除します。

13
Adam Gent

これを行う標準的な方法は(Spring JDBCを使用している場合) org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate クラスを使用することです。

このクラスを使用すると、SQLパラメーターとしてリストを定義し、NamedParameterJdbcTemplateを使用して名前付きパラメーターを置き換えることができます。例えば:

public List<MyObject> getDatabaseObjects(List<String> params) {
    NamedParameterJdbcTemplate jdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
    String sql = "select * from my_table where my_col in (:params)";
    List<MyObject> result = jdbcTemplate.query(sql, Collections.singletonMap("params", params), myRowMapper);
    return result;
}
8
Richard

これを解決するには、検索する値がある数の?でSQL文字列を作成しました。

SELECT * FROM MYTABLE WHERE MYCOL in (?,?,?,?)

最初に、ステートメントに渡すことができる配列タイプを検索しましたが、すべてのJDBC配列タイプはベンダー固有です。だから私は複数の?にとどまりました。

3
tangens

docs.spring(19.7.3) から答えを得ました

標準SQLでは、変数の値リストを含む式に基づいて行を選択できます。典型的な例は、select * from T_ACTOR where id in(1、2、3)です。この変数リストは、JDBC標準によって準備済みステートメントに対して直接サポートされていません。可変数のプレースホルダーを宣言することはできません。必要な数のプレースホルダーを用意したさまざまなバリエーションが必要です。または、必要なプレースホルダーの数がわかったら、SQL文字列を動的に生成する必要があります。 NamedParameterJdbcTemplateおよびJdbcTemplateで提供される名前付きパラメーターのサポートは、後者のアプローチを取ります。プリミティブオブジェクトのJava.util.Listとして値を渡します。このリストは、ステートメントの実行中に必要なプレースホルダーを挿入し、値を渡すために使用されます。

これがあなたのお役に立てば幸いです。

2
guangleiw

私の知る限り、JDBCにはコレクションをパラメーターとして処理するための標準サポートはありません。リストを渡すだけでいいのなら、それは拡張されるでしょう。

SpringのJDBCアクセスは、コレクションをパラメーターとして渡すことをサポートしています。これを安全にコーディングするためのインスピレーションのためにこれがどのように行われるかを見ることができます。

JDBCパラメータとしてのコレクションの自動展開 を参照してください

(最初にHibernateについて説明し、次にJDBCについて説明します。)

1
mdma

私の試用版とItの成功をご覧ください。リストのサイズには潜在的な制限があると言われています。リストl = Arrays.asList(new Integer [] {12496,12497,12498,12499}); Map param = Collections.singletonMap( "goodsid"、l);

    NamedParameterJdbcTemplate  namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(getJdbcTemplate().getDataSource());
    String sql = "SELECT bg.goodsid FROM beiker_goods bg WHERE bg.goodsid in(:goodsid)";
    List<Long> list = namedParameterJdbcTemplate.queryForList(sql, param2, Long.class);
0
janwen

sormulaはこれを簡単にします( 例4 を参照):

ArrayList<Integer> partNumbers = new ArrayList<Integer>();
partNumbers.add(999);
partNumbers.add(777);
partNumbers.add(1234);

// set up
Database database = new Database(getConnection());
Table<Inventory> inventoryTable = database.getTable(Inventory.class);

// select operation for list "...WHERE PARTNUMBER IN (?, ?, ?)..."
for (Inventory inventory: inventoryTable.
    selectAllWhere("partNumberIn", partNumbers))    
{
    System.out.println(inventory.getPartNumber());
}
0
Jeff Miller

使用できるさまざまな代替アプローチがあります。

  1. 単一クエリの実行-時間がかかるためお勧めしません
  2. ストアドプロシージャの使用-データベース固有
  3. PreparedStatementクエリを動的に作成する-パフォーマンスは良いが、キャッシングの利点はほとんどなく、再コンパイルが必要
  4. PreparedStatementクエリでのNULLの使用-これは最適なパフォーマンスを備えた優れたアプローチだと思います。

これらの詳細を確認してください こちら

0
Pankaj