Как использовать подготовленные операторы в SQlite в Android?
Я постоянно использую подготовленные операторы в Android, это довольно просто:
SQLiteDatabase db = dbHelper.getWritableDatabase();
SQLiteStatement stmt = db.compileStatement("INSERT INTO Country (code) VALUES (?)");
stmt.bindString(1, "US");
stmt.executeInsert();
Пример с jasonhudgins не работает. Вы не можете выполнить запрос с помощью stmt.execute() и получить обратно значение (или Cursor).
Вы можете предварительно скомпилировать только операторы, которые либо вообще не возвращают строк (например, оператор вставки или создания таблицы), либо одну строку и столбец (и используйте simpleQueryForLong() или simpleQueryForString()).
Если вам нужен курсор при возврате, вы можете рассмотреть что-то вроде этого:
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"));
См. Этот фрагмент Генскрипты, если вам нужен более полный. Обратите внимание, что это параметризованный SQL-запрос, поэтому, по сути, это подготовленный оператор.
Вам нужно переместить курсор, прежде чем вы сможете получить данные
Чтобы получить курсор, вы не можете использовать compiledStatement. Однако, если вы хотите использовать полностью подготовленный оператор SQL, я рекомендую адаптировать метод jbaez ... Использование db.rawQuery() вместо db.query().
Для подготовленных операторов SQLite в Android есть SQLiteStatement. Подготовленные операторы помогают повысить производительность (особенно для операторов, которые необходимо выполнять несколько раз), а также помогают избежать атак с использованием инъекций. См. эта статья для общего обсуждения подготовленных операторов.
SQLiteStatement предназначен для использования с операторами SQL, которые не возвращают несколько значений. (Это означает, что вы не будете использовать их для большинства запросов.) Ниже приведены некоторые примеры:
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, поскольку эти возвращаемые значения. (Но см. этот вопрос.)
String sql = "INSERT INTO table_name (column_1, column_2) VALUES (57, 'hello')";
SQLiteStatement statement = db.compileStatement(sql);
long rowId = statement.executeInsert();
Обратите внимание, что используется метод executeInsert(), а не execute(). Конечно, вы не захотите всегда вводить одни и те же данные в каждую строку. Для этого вы можете использовать привязки.
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 не нужно каждый раз анализировать и компилировать. Вы можете ускорить процесс еще больше, используя сделки. Это позволяет сразу применить все изменения. Вот пример:
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();
}
Ознакомьтесь с этими ссылками, чтобы получить более подробную информацию о транзакциях и ускорении вставки в базу данных.
Это простой пример. Вы также можете применить концепции из раздела выше.
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();
Метод executeUpdateDelete() также может использоваться для операторов DELETE и был представлен в API 11. См. Этот вопрос и ответ.
Вот пример.
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();
}
Обычно, когда вы запускаете запрос, вы хотите вернуть курсор с большим количеством строк. Однако SQLiteStatement предназначен не для этого. Вы не запускаете с ним запрос, если вам не нужен только простой результат, например количество строк в базе данных, что вы можете сделать с помощью simpleQueryForLong()
String sql = "SELECT COUNT(*) FROM table_name";
SQLiteStatement statement = db.compileStatement(sql);
long result = statement.simpleQueryForLong();
Обычно вы запускаете метод query()SQLiteDatabase, чтобы получить курсор.
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);
См. этот ответ для получения более подробной информации о запросах.
Напоминаем: все методы .bindString / .bindLong / ... основаны на единице.
Я искал под капотом удобные методы Android, такие как .query, .insert и .delete, и заметил, что они используют SQLiteStatement под капотом. Разве не было бы проще использовать удобные методы вместо построения собственных операторов?
@ NicolásCarrasco, я давно не работал над этим, так что теперь я немного заржавел. Для запросов и одиночных вставок, обновлений и удалений я бы определенно использовал удобные методы. Однако, если вы выполняете массовые вставки, обновления или удаления, я бы рассмотрел подготовленные операторы вместе с транзакцией. Что касается того, что SQLiteStatement используется под капотом, и достаточно ли хороши удобные методы, я не могу об этом говорить. Думаю, я бы сказал, что если удобные методы работают для вас достаточно быстро, используйте их.
Почему вы используете clearBindings ()? Вы связываете все свои ценности без каких-либо условий. Для меня не имеет смысла сначала устанавливать для них значение null, а для реального значения.
@TheincredibleJan, я точно не знаю. Возможно, в этом нет необходимости, и вы можете попробовать это, не очищая привязки, чтобы увидеть, имеет ли это значение. Однако при этом вызов clearBindings() не просто устанавливает для них null (см. исходный код). Я смотрю на это как на очистку состояния, чтобы ничего не влияло на него из предыдущего цикла. Хотя, возможно, в этом нет необходимости. Буду рад, если кто-нибудь прокомментирует.
Небольшая ошибка в приведенном выше коде: это должно быть «new String [] {strCountryCode}» вместо «new String {strCountryCode}».