web-dev-qa-db-fra.com

Quelle est la meilleure approche en utilisant JDBC pour paramétrer une clause IN?

Dites que j'ai une requête du formulaire

SELECT * FROM MYTABLE WHERE MYCOL in (?)

Et je veux paramétrer les arguments dans.

Existe-t-il un moyen simple de le faire dans Java avec JDBC, d'une manière qui pourrait fonctionner sur plusieurs bases de données sans modifier le SQL lui-même?

La plus proche la question que j'ai trouvée concernait C # , je me demande s'il y a quelque chose de différent pour Java/JDBC.

37
Uri

Il n'y a en effet aucun moyen simple de le faire dans JDBC. Certains pilotes JDBC semblent prendre en charge PreparedStatement#setArray() sur la clause IN. Je ne sais pas exactement lesquels.

Vous pouvez simplement utiliser une méthode d'assistance avec String#join() et Collections#nCopies() pour générer les espaces réservés pour la clause IN et une autre méthode d'aide pour définir toutes les valeurs dans une boucle avec 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]);
    }
}

Voici comment vous pouvez l'utiliser:

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;
}

Notez que certaines bases de données ont une limite de quantité autorisée de valeurs dans la clause IN. Oracle, par exemple, a cette limite sur 1000 articles.

43
BalusC

Puisque personne ne répond au cas pour un grande clause IN (plus de 100) je vais jeter ma solution à ce problème qui fonctionne bien pour JDBC. En bref, je remplace le IN par un INNER JOIN sur une table tmp.

Ce que je fais est de créer ce que j'appelle une table d'identifiants de lots et en fonction du SGBDR, je peux en faire une table tmp ou une table en mémoire.

Le tableau comporte deux colonnes. Une colonne avec l'ID de la clause IN et une autre colonne avec un ID de lot que je génère à la volée.

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

Avant de sélectionner, insérez vos identifiants dans le tableau avec un identifiant de lot donné. Ensuite, il vous suffit de remplacer votre clause IN de requêtes d'origine par une correspondance INNER JOIN sur votre table d'ID WHERE batch_id est égal à votre lot actuel. Après avoir terminé, supprimez les entrées pour votre lot.

13
Adam Gent

La manière standard de procéder est (si vous utilisez Spring JDBC) d'utiliser la classe org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate .

En utilisant cette classe, il est possible de définir une liste comme paramètre SQL et d'utiliser le NamedParameterJdbcTemplate pour remplacer un paramètre nommé. Par exemple:

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

J'ai résolu cela en construisant la chaîne SQL avec autant de ? car j'ai des valeurs à rechercher.

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

J'ai d'abord recherché un type de tableau que je peux passer dans l'instruction, mais tous les types de tableau JDBC sont spécifiques au fournisseur. Je suis donc resté avec les multiples ?.

3
tangens

J'ai obtenu la réponse de docs.spring (19.7.3)

Le standard SQL permet de sélectionner des lignes en fonction d'une expression qui inclut une liste de valeurs variables. Un exemple typique serait select * from T_ACTOR where id in (1, 2, 3). Cette liste de variables n'est pas directement prise en charge pour les instructions préparées par la norme JDBC; vous ne pouvez pas déclarer un nombre variable d'espaces réservés. Vous avez besoin d'un certain nombre de variantes avec le nombre souhaité d'espaces réservés préparés, ou vous devez générer la chaîne SQL dynamiquement une fois que vous savez combien d'espaces réservés sont nécessaires. La prise en charge des paramètres nommés fournie dans NamedParameterJdbcTemplate et JdbcTemplate adopte cette dernière approche. Transmettez les valeurs en tant que Java.util.List d'objets primitifs. Cette liste sera utilisée pour insérer les espaces réservés requis et transmettre les valeurs lors de l'exécution de l'instruction.

J'espère que cela peut vous aider.

2
guangleiw

AFAIK, il n'y a pas de prise en charge standard dans JDBC pour gérer les collections en tant que paramètres. Ce serait formidable si vous pouviez simplement passer une liste et cela serait étendu.

L'accès JDBC de Spring prend en charge le passage de collections en tant que paramètres. Vous pouvez voir comment cela se fait pour vous inspirer à coder cela en toute sécurité.

Voir collections à expansion automatique en tant que paramètres JDBC

(L'article aborde d'abord Hibernate, puis continue avec JDBC.)

1
mdma

Voir mon essai et son succès, il est dit que la taille de la liste a une limitation potentielle. List 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 rend cela simple (voir Exemple 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

Il existe différentes approches alternatives que nous pouvons utiliser.

  1. Exécuter des requêtes uniques - lent et non recommandé
  2. Utilisation de la procédure stockée - spécifique à la base de données
  3. Création dynamique d'une requête PreparedStatement - bonnes performances mais avantages lâches de la mise en cache et besoin d'une recompilation
  4. Utilisation de NULL dans la requête PreparedStatement - Je pense que c'est une bonne approche avec des performances optimales.

Vérifiez plus de détails sur ces ici .

0
Pankaj