Comment utiliser des instructions préparées dans SQlite sous Android?

100

Comment utiliser des instructions préparées dans SQlite sous Android?

pupeno
la source
9
Pensez à changer la réponse acceptée.
Suragch le
9
d'accord - envisagez de changer la réponse ... la mentalité du troupeau a voté à la hausse la réponse acceptée, lorsqu'une meilleure solution existe.
goodguys_activate
4
Pablo, veuillez changer la réponse acceptée ... l'exemple donné ne fonctionne même pas. Et nos votes négatifs ne suffisent pas à le détacher.
SparK

Réponses:

21

J'utilise tout le temps des déclarations préparées sous Android, c'est assez simple:

SQLiteDatabase db = dbHelper.getWritableDatabase();
SQLiteStatement stmt = db.compileStatement("INSERT INTO Country (code) VALUES (?)");
stmt.bindString(1, "US");
stmt.executeInsert();
Jasonhudgins
la source
81
Je ne sais pas comment cette réponse peut avoir autant de voix. SQLIteStatement # execute ne doit pas être utilisé pour les requêtes SQL, uniquement les instructions. Veuillez consulter developer.android.com/reference/android/database/sqlite/…
simao le
9
Alors, comment êtes-vous censé utiliser une instruction préparée pour interroger des données?
Juan Mendes
12
Notez qu'il SQLiteStatement.bindXXX()a un index basé sur 1, pas basé sur 0 comme la plupart des.
Simulant
6
@jasonhudgins Pourquoi ne pas simplement remplacer votre SELECT par un INSERT? Je viens de
sortir
35
exemple parfait de la mentalité de troupeau qui vote et accepte une réponse complètement incorrecte
165

Pour les instructions SQLite préparées sous Android, il existe SQLiteStatement . Les instructions préparées vous aident à accélérer les performances (en particulier pour les instructions qui doivent être exécutées plusieurs fois) et à éviter les attaques par injection. Consultez cet article pour une discussion générale sur les déclarations préparées.

SQLiteStatementest destiné à être utilisé avec des instructions SQL qui ne renvoient pas plusieurs valeurs. (Cela signifie que vous ne les utiliserez pas pour la plupart des requêtes.) Voici quelques exemples:

Créer une table

String sql = "CREATE TABLE table_name (column_1 INTEGER PRIMARY KEY, column_2 TEXT)";
SQLiteStatement stmt = db.compileStatement(sql);
stmt.execute();

La execute()méthode ne renvoie pas de valeur, il est donc approprié de l'utiliser avec CREATE et DROP, mais n'est pas destinée à être utilisée avec SELECT, INSERT, DELETE et UPDATE car ces valeurs retournent. (Mais voyez cette question .)

Insérer des valeurs

String sql = "INSERT INTO table_name (column_1, column_2) VALUES (57, 'hello')";
SQLiteStatement statement = db.compileStatement(sql);
long rowId = statement.executeInsert();

Notez que la executeInsert()méthode est utilisée plutôt que execute(). Bien sûr, vous ne voudriez pas toujours entrer les mêmes choses dans chaque ligne. Pour cela, vous pouvez utiliser des liaisons .

String sql = "INSERT INTO table_name (column_1, column_2) VALUES (?, ?)";
SQLiteStatement statement = db.compileStatement(sql);

int intValue = 57;
String stringValue = "hello";

statement.bindLong(1, intValue); // 1-based: matches first '?' in sql string
statement.bindString(2, stringValue);  // matches second '?' in sql string

long rowId = statement.executeInsert();

Habituellement, vous utilisez des instructions préparées lorsque vous souhaitez répéter rapidement quelque chose (comme un INSERT) plusieurs fois. L'instruction préparée fait en sorte que l'instruction SQL n'ait pas à être analysée et compilée à chaque fois. Vous pouvez accélérer les choses encore plus en utilisant des transactions . Cela permet d'appliquer toutes les modifications en même temps. Voici un exemple:

String stringValue = "hello";
try {

    db.beginTransaction();
    String sql = "INSERT INTO table_name (column_1, column_2) VALUES (?, ?)";
    SQLiteStatement statement = db.compileStatement(sql);

    for (int i = 0; i < 1000; i++) {
        statement.clearBindings();
        statement.bindLong(1, i);
        statement.bindString(2, stringValue + i);
        statement.executeInsert();
    }

    db.setTransactionSuccessful(); // This commits the transaction if there were no exceptions

} catch (Exception e) {
    Log.w("Exception:", e);
} finally {
    db.endTransaction();
}

Consultez ces liens pour plus d'informations sur les transactions et l'accélération des insertions de bases de données.

Mettre à jour les lignes

Ceci est un exemple basique. Vous pouvez également appliquer les concepts de la section ci-dessus.

String sql = "UPDATE table_name SET column_2=? WHERE column_1=?";
SQLiteStatement statement = db.compileStatement(sql);

int id = 7;
String stringValue = "hi there";

statement.bindString(1, stringValue);
statement.bindLong(2, id);

int numberOfRowsAffected = statement.executeUpdateDelete();

Supprimer des lignes

La executeUpdateDelete()méthode peut également être utilisée pour les instructions DELETE et a été introduite dans l'API 11. Voir cette Q&R .

Voici un exemple.

try {

    db.beginTransaction();
    String sql = "DELETE FROM " + table_name +
            " WHERE " + column_1 + " = ?";
    SQLiteStatement statement = db.compileStatement(sql);

    for (Long id : words) {
        statement.clearBindings();
        statement.bindLong(1, id);
        statement.executeUpdateDelete();
    }

    db.setTransactionSuccessful();

} catch (SQLException e) {
    Log.w("Exception:", e);
} finally {
    db.endTransaction();
}

Requete

Normalement, lorsque vous exécutez une requête, vous souhaitez récupérer un curseur avec beaucoup de lignes. Ce n'est pas pour ça SQLiteStatement, cependant. Vous n'exécutez pas de requête avec lui, sauf si vous n'avez besoin que d'un résultat simple, comme le nombre de lignes dans la base de données, que vous pouvez faire avecsimpleQueryForLong()

String sql = "SELECT COUNT(*) FROM table_name";
SQLiteStatement statement = db.compileStatement(sql);
long result = statement.simpleQueryForLong();

Habituellement, vous exécuterez la query()méthode de SQLiteDatabase pour obtenir un curseur.

SQLiteDatabase db = dbHelper.getReadableDatabase();
String table = "table_name";
String[] columnsToReturn = { "column_1", "column_2" };
String selection = "column_1 =?";
String[] selectionArgs = { someValue }; // matched to "?" in selection
Cursor dbCursor = db.query(table, columnsToReturn, selection, selectionArgs, null, null, null);

Consultez cette réponse pour plus de détails sur les requêtes.

Suragch
la source
5
Juste un rappel: les méthodes .bindString / .bindLong / ... sont toutes basées sur 1.
Denys Vitali
Je regardais sous le capot des méthodes de commodité Android telles que .query, .insert et .delete et j'ai remarqué qu'elles utilisent SQLiteStatement sous le capot. Ne serait-il pas plus facile d'utiliser simplement des méthodes pratiques au lieu de créer vos propres déclarations?
Nicolás Carrasco
@ NicolásCarrasco, cela fait un moment que je n'ai pas travaillé là-dessus donc je suis un peu rouillé maintenant. Pour les requêtes et les insertions simples, les mises à jour et les suppressions, j'utiliserais certainement les méthodes pratiques. Cependant, si vous effectuez des insertions, des mises à jour ou des suppressions en masse, je considérerais des déclarations préparées avec une transaction. Quant à SQLiteStatement utilisé sous le capot et si les méthodes de commodité sont assez bonnes, je ne peux pas en parler. Je suppose que je dirais que si les méthodes pratiques fonctionnent assez rapidement pour vous, utilisez-les.
Suragch le
Pourquoi utilisez-vous clearBindings ()? Vous liez toutes vos valeurs sans aucune condition. Cela n'a pas de sens pour moi de les définir d'abord sur null et sur la valeur réelle.
L'incroyable janvier
@TheincredibleJan, je ne sais pas avec certitude. Cela peut ne pas être nécessaire et vous pouvez l'essayer sans effacer les liaisons pour voir si cela fait une différence. Cependant, cela dit, les appels clearBindings()ne les définissent pas seulement sur null(voir le code source ). Je vois cela comme un effacement de l'état afin que rien ne l'influençât de la boucle précédente. Ce n'est peut-être pas nécessaire, cependant. Je serais heureux que quelqu'un sache commenter.
Suragch
22

Si vous voulez un curseur au retour, vous pouvez envisager quelque chose comme ceci:

SQLiteDatabase db = dbHelper.getWritableDatabase();

public Cursor fetchByCountryCode(String strCountryCode)
{
    /**
     * SELECT * FROM Country
     *      WHERE code = US
     */
    return cursor = db.query(true, 
        "Country",                        /**< Table name. */
        null,                             /**< All the fields that you want the 
                                                cursor to contain; null means all.*/
        "code=?",                         /**< WHERE statement without the WHERE clause. */
        new String[] { strCountryCode },    /**< Selection arguments. */
        null, null, null, null);
}

/** Fill a cursor with the results. */
Cursor c = fetchByCountryCode("US");

/** Retrieve data from the fields. */
String strCountryCode = c.getString(cursor.getColumnIndex("code"));

/** Assuming that you have a field/column with the name "country_name" */
String strCountryName = c.getString(cursor.getColumnIndex("country_name"));

Voir cet extrait de Genscripts au cas où vous en voudriez un plus complet. Notez qu'il s'agit d'une requête SQL paramétrée, c'est donc essentiellement une instruction préparée.

jbaez
la source
Petite erreur dans le code ci-dessus: il devrait s'agir de "new String [] {strCountryCode}" au lieu de "new String {strCountryCode}".
Pierre-Luc Simard
Vous devez déplacer le curseur avant de pouvoir récupérer les données
Chin
9

L'exemple de jasonhudgins ne fonctionnera pas. Vous ne pouvez pas exécuter une requête avec stmt.execute()et récupérer une valeur (ou a Cursor).

Vous ne pouvez précompiler que les instructions qui ne retournent aucune ligne (comme une instruction insert ou create table) ou une seule ligne et colonne (et utilisez simpleQueryForLong()ou simpleQueryForString()).

sébaste64
la source
1

Pour obtenir un curseur, vous ne pouvez pas utiliser un compiledStatement. Cependant, si vous souhaitez utiliser une instruction SQL préparée complète, je recommande une adaptation de la méthode de jbaez ... Utiliser db.rawQuery()au lieu de db.query().

Aaron
la source