【问题标题】:How do I use prepared statements in SQlite in Android?如何在 Android 的 SQlite 中使用准备好的语句?
【发布时间】:2010-09-30 19:25:07
【问题描述】:

如何在 Android 的 SQlite 中使用准备好的语句?

【问题讨论】:

    标签: android sqlite prepared-statement


    【解决方案1】:

    对于 Android 中准备好的 SQLite 语句,有 SQLiteStatement。准备好的语句可以帮助您提高性能(尤其是对于需要多次执行的语句),还可以帮助避免注入攻击。请参阅this article,了解有关准备好的语句的一般性讨论。

    SQLiteStatement 用于不返回多个值的 SQL 语句。 (这意味着您不会将它们用于大多数查询。)以下是一些示例:

    Create a table

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

    execute() 方法不返回值,因此适合与 CREATE 和 DROP 一起使用,但不适合与 SELECT、INSERT、DELETE 和 UPDATE 一起使用,因为这些返回值。 (但请参阅this question。)

    Insert values

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

    请注意,使用的是executeInsert() 方法而不是execute()。当然,您不希望总是在每一行中输入相同的内容。为此,您可以使用bindings

    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();
    

    当您想要多次快速重复某事(如 INSERT)时,通常会使用准备好的语句。准备好的语句使得 SQL 语句不必每次都被解析和编译。您可以使用transactions 加快速度。这允许一次应用所有更改。这是一个例子:

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

    查看这些链接以获取有关事务和加速数据库插入的更多信息。

    Update rows

    这是一个基本示例。您也可以应用上一节中的概念。

    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();
    

    Delete rows

    executeUpdateDelete() 方法也可用于 DELETE 语句,并在 API 11 中引入。See this Q&A

    这是一个例子。

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

    Query

    通常,当您运行查询时,您希望返回包含大量行的游标。不过,这不是SQLiteStatement 的用途。除非您只需要一个简单的结果,例如数据库中的行数,否则您不会使用它运行查询,您可以使用 simpleQueryForLong()

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

    通常你会运行SQLiteDatabasequery()方法来获取光标。

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

    有关查询的更多详细信息,请参阅this answer

    【讨论】:

    • 提醒一下:.bindString / .bindLong / ... 方法都是从 1 开始的。
    • 我正在查看诸如 .query、.insert 和 .delete 之类的 Android 便利方法的底层,并注意到它们在底层使用 SQLiteStatement。使用方便的方法而不是构建自己的语句不是更容易吗?
    • @NicolásCarrasco,我已经有一段时间没有做这个了,所以我现在有点生疏了。对于查询和单个插入、更新和删除,我肯定会使用方便的方法。但是,如果您要进行大量插入、更新或删除,我会考虑将准备好的语句与事务一起考虑。至于在后台使用 SQLiteStatement 以及方便的方法是否足够好,我不能说。我想我会说,如果方便方法对您来说执行得足够快,那就使用它们。
    • 为什么要使用 clearBindings()?您没有任何条件地绑定所有值。首先将它们设置为 null 并将其设置为实际值对我来说没有意义。
    • @TheincredibleJan,我不确定。这可能不是必需的,您可以在不清除绑定的情况下尝试它,看看它是否有所作为。然而,话虽如此,调用clearBindings() 并不仅仅将它们设置为null(参见source code)。我将其视为清除状态,以便前一个循环不会影响它。不过,也许这不是必需的。我会很高兴有人知道发表评论。
    【解决方案2】:

    我一直在 Android 中使用准备好的语句,这很简单:

    SQLiteDatabase db = dbHelper.getWritableDatabase();
    SQLiteStatement stmt = db.compileStatement("INSERT INTO Country (code) VALUES (?)");
    stmt.bindString(1, "US");
    stmt.executeInsert();
    

    【讨论】:

      【解决方案3】:

      如果你想要一个光标返回,那么你可以考虑这样的事情:

      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"));
      

      如果您想要更完整的,请参阅此 sn-p Genscripts。请注意,这是一个参数化的 SQL 查询,因此本质上它是一个准备好的语句。

      【讨论】:

      • 上面代码中的小错误:应该是“new String[] { strCountryCode }”,而不是“new String { strCountryCode }”。
      • 您需要移动光标才能检索数据
      【解决方案4】:

      jasonhudgins 示例不起作用。您无法使用stmt.execute() 执行查询并返回值(或Cursor)。

      您只能预编译根本不返回任何行(例如插入或创建表语句)或单个行和列(并使用simpleQueryForLong()simpleQueryForString())的语句。

      【讨论】:

        【解决方案5】:

        要获得游标,您不能使用已编译的语句。但是,如果你想使用完整的准备好的 SQL 语句,我建议改编 jbaez 的方法...使用 db.rawQuery() 而不是 db.query()

        【讨论】:

          猜你喜欢
          • 2014-03-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多