9

正如 Android 文档所说,rawQuery 方法的 selectionArgs 参数被解析为字符串。

SQLiteDatabase.rawQuery(String sql, String[] selectionArgs)

selectionArgs:您可以在查询的 where 子句中包含 ?s,它将被 selectionArgs 中的值替换。这些值将绑定为字符串。

但是今天,我遇到了一个问题,这个问题占据了我大部分时间。想象以下查询:

SELECT * FROM TABLE_A WHERE IFNULL(COLUMN_A, 0) >= 15

COLUMN_A 是整数。该表有大约 10 行符合该标准。在数据库编辑器上运行查询,结果总是正确的,但在智能手机上,语句总是不返回任何行。

一段时间后,a 将查询更改为:

SELECT * FROM TABLE_A WHERE IFNULL(COLUMN_A, 0) >= '15'

并且编辑器没有返回任何行,就像 Android 一样。因此,将查询更改为:

SELECT * FROM TABLE_A WHERE CAST(IFNULL(COLUMN_A, 0) as INTEGER) >= '15'

解决了这个问题。进行的另一项测试是:

SELECT * FROM TABLE_A WHERE COLUMN_A >= '15'

此外,返回了正确的结果。

这似乎是一个问题,涉及 Android 使用 IFNULL 子句将参数绑定到查询(作为字符串)的方式。

那么,有人知道为什么会这样吗?有什么建议可以在不使用 CAST 的情况下解决这个问题吗?

4

2 回答 2

11

将值绑定到查询的原因是为了防止SQL 注入攻击

基本上,您将查询(包括占位符)发送到数据库并说“我的下一个查询将采用这种形式,而不是其他形式!”。当攻击者随后注入不同的查询字符串(例如,通过表单字段)时,数据库会显示“嘿,这不是您说要发送的查询!” 并向您抛出错误。

由于命令(可以像链接文章中所示注入到您的实际查询中)是字符串,因此字符串数据类型是更“危险”的一种。如果用户尝试将一些代码注入到您的字段中,该代码应该只接受数字,并且您尝试将输入转换/解析为整数(在将值放入查询之前),您将立即收到异常。使用字符串,就没有这种安全性。因此,他们可能必须逃脱。这可能是绑定值都被解释为字符串的原因。

以上是的!无论您绑定的参数是字符串还是整数,它们都同样危险。此外,在代码中预先检查您的值会导致大量样板代码,容易出错且不灵活!


为了防止您的应用程序发生 SQL 注入并加快多个数据库写入操作(使用具有不同值的相同查询),您应该使用“准备好的语句”。在 Android SDK 中写入数据库的正确类是SQLiteStatement.

要创建准备好的语句,请使用-object的compileStatement()-method并使用正确的-method(继承自)SQLiteDatabase绑定相应的值(将?在查询中替换为 -marks ):bindXX()SQLiteProgram

SQLiteDatabase db = dbHelper.getWritableDatabase();
SQLiteStatement stmt = db.compileStatement("INSERT INTO SomeTable (name, age) values (?,?)");
// Careful! The index begins at 1, not 0 !!
stmt.bindString(1, "Jon");
stmt.bindLong(2, 48L);
stmt.execute();
// Also important! Clean up after yourself.
stmt.close();

取自这个较早的问题的示例:如何在 Android 的 SQlite 中使用准备好的语句?


可悲的是,SQLiteStatement没有返回 a 的重载Cursor,因此您不能将它用于SELECT- 语句。对于那些,您可以使用rawQuery(String, String[])- 方法SQLiteDatabase

int number = getNumberFromUser();
String[] arguments = new String[]{String.valueOf(number)};
db.rawQuery("SELECT * FROM TABLE_A WHERE IFNULL(COLUMN_A, 0) >= ?", arguments);

请注意,rawQuery()-method 采用字符串数组作为参数值。这其实没关系,SQLite 会自动转换为正确的类型。只要字符串表示等于您在 SQL 查询中所期望的,就可以了。

于 2012-07-04T00:00:31.503 回答
1

我猜我有完全相同的问题。您要提及的是您无法对 sqlite 数据库进行实际计算。我发现问题比你提到的更大。SQLite 数据库似乎不了解有关字段值的任何字段类型。这意味着如果您尝试在 INTEGER 字段类型中插入 String 值,则插入不会抱怨。

因此,如您所见,问题更大。虽然我已经看到,如果你有一列只有整数,并且你做了一个 where 语句,比如: where id = 1 without ' ' 那么就有一个结果数据集。所以我可能会问你是否确定这个语句不起作用:“SELECT * FROM TABLE_A WHERE IFNULL(COLUMN_A, 0) >= 15”。但是 where id >= '15' 确实有效,因为它采用 id 的字符串表示形式,它是实际的 2 个 unicode 字符(!!!),并尝试将运算符 >= 设为适用的 '15'。

我第一次遇到这些问题时让我感到惊讶,我决定动态创建 SQL 而不绑定参数并作为一个完整的字符串执行。我知道在没有绑定参数的情况下,这不是访问数据库的最佳方式,但它是一种解决方案,而且是一个很好的解决方案,因为尽管您的数据库是安全的并且您的访问方法对您的应用程序来说是私有的,但安全原因并不那么重要. 如果“入侵者”能够通过 root 电话访问数据库,他只需将其放入 SQLITE Studio 即可。

于 2012-07-04T00:10:54.023 回答