Сохранение данных в базе данных идеально подходит для повторяющихся или структурированных данных, таких как контактная информация. На этой странице предполагается, что вы знакомы с базами данных SQL в целом, и она поможет вам начать работу с базами данных SQLite на Android. API-интерфейсы, необходимые для использования базы данных на Android, доступны в пакете android.database.sqlite
.
Внимание: хотя эти API являются мощными, они довольно низкоуровневые и требуют много времени и усилий для использования:
- Проверка необработанных SQL-запросов во время компиляции не осуществляется. По мере изменения графика данных вам необходимо обновить затронутые SQL-запросы вручную. Этот процесс может занять много времени и привести к ошибкам.
- Вам нужно использовать много шаблонного кода для преобразования между запросами SQL и объектами данных.
По этим причинам мы настоятельно рекомендуем использовать библиотеку Room Persistence Library в качестве уровня абстракции для доступа к информации в базах данных SQLite вашего приложения.
Определить схему и контракт
Одним из основных принципов баз данных SQL является схема: формальное объявление того, как организована база данных. Схема отражается в операторах SQL, которые вы используете для создания базы данных. Возможно, вам будет полезно создать сопутствующий класс, известный как класс контракта , который явно определяет структуру вашей схемы систематическим и самодокументируемым способом.
Класс контракта — это контейнер для констант, определяющих имена URI, таблиц и столбцов. Класс контракта позволяет вам использовать одни и те же константы во всех других классах одного пакета. Это позволяет вам изменить имя столбца в одном месте и распространить его по всему коду.
Хороший способ организовать класс контракта — поместить определения, которые являются глобальными для всей вашей базы данных, на корневом уровне класса. Затем создайте внутренний класс для каждой таблицы. Каждый внутренний класс перечисляет столбцы соответствующей таблицы.
Примечание. Реализуя интерфейс BaseColumns
, ваш внутренний класс может наследовать поле первичного ключа с именем _ID
, которое некоторые классы Android, такие как CursorAdapter
, ожидают от него. Это не обязательно, но это поможет вашей базе данных гармонично работать с платформой Android.
Например, следующий контракт определяет имя таблицы и имена столбцов для одной таблицы, представляющей RSS-канал:
Котлин
object FeedReaderContract { // Table contents are grouped together in an anonymous object. object FeedEntry : BaseColumns { const val TABLE_NAME = "entry" const val COLUMN_NAME_TITLE = "title" const val COLUMN_NAME_SUBTITLE = "subtitle" } }
Ява
public final class FeedReaderContract { // To prevent someone from accidentally instantiating the contract class, // make the constructor private. private FeedReaderContract() {} /* Inner class that defines the table contents */ public static class FeedEntry implements BaseColumns { public static final String TABLE_NAME = "entry"; public static final String COLUMN_NAME_TITLE = "title"; public static final String COLUMN_NAME_SUBTITLE = "subtitle"; } }
Создайте базу данных с помощью помощника SQL
После того, как вы определили, как выглядит ваша база данных, вам следует реализовать методы, которые создают и поддерживают базу данных и таблицы. Вот несколько типичных операторов создания и удаления таблицы:
Котлин
private const val SQL_CREATE_ENTRIES = "CREATE TABLE ${FeedEntry.TABLE_NAME} (" + "${BaseColumns._ID} INTEGER PRIMARY KEY," + "${FeedEntry.COLUMN_NAME_TITLE} TEXT," + "${FeedEntry.COLUMN_NAME_SUBTITLE} TEXT)" private const val SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS ${FeedEntry.TABLE_NAME}"
Ява
private static final String SQL_CREATE_ENTRIES = "CREATE TABLE " + FeedEntry.TABLE_NAME + " (" + FeedEntry._ID + " INTEGER PRIMARY KEY," + FeedEntry.COLUMN_NAME_TITLE + " TEXT," + FeedEntry.COLUMN_NAME_SUBTITLE + " TEXT)"; private static final String SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS " + FeedEntry.TABLE_NAME;
Как и файлы, которые вы сохраняете во внутренней памяти устройства, Android хранит вашу базу данных в личной папке вашего приложения. Ваши данные в безопасности, поскольку по умолчанию эта область недоступна другим приложениям или пользователю.
Класс SQLiteOpenHelper
содержит полезный набор API для управления базой данных. Когда вы используете этот класс для получения ссылок на вашу базу данных, система выполняет потенциально длительные операции по созданию и обновлению базы данных только при необходимости, а не во время запуска приложения . Все, что вам нужно сделать, это вызвать getWritableDatabase()
или getReadableDatabase()
.
Примечание. Поскольку они могут выполняться долго, обязательно вызывайте getWritableDatabase()
или getReadableDatabase()
в фоновом потоке. Дополнительную информацию см. в разделе Потоки на Android .
Чтобы использовать SQLiteOpenHelper
, создайте подкласс, который переопределяет методы обратного вызова onCreate()
и onUpgrade()
. Вы также можете реализовать методы onDowngrade()
или onOpen()
, но они не являются обязательными.
Например, вот реализация SQLiteOpenHelper
, которая использует некоторые из команд, показанных выше:
Котлин
class FeedReaderDbHelper(context: Context) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) { override fun onCreate(db: SQLiteDatabase) { db.execSQL(SQL_CREATE_ENTRIES) } override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { // This database is only a cache for online data, so its upgrade policy is // to simply to discard the data and start over db.execSQL(SQL_DELETE_ENTRIES) onCreate(db) } override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { onUpgrade(db, oldVersion, newVersion) } companion object { // If you change the database schema, you must increment the database version. const val DATABASE_VERSION = 1 const val DATABASE_NAME = "FeedReader.db" } }
Ява
public class FeedReaderDbHelper extends SQLiteOpenHelper { // If you change the database schema, you must increment the database version. public static final int DATABASE_VERSION = 1; public static final String DATABASE_NAME = "FeedReader.db"; public FeedReaderDbHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } public void onCreate(SQLiteDatabase db) { db.execSQL(SQL_CREATE_ENTRIES); } public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // This database is only a cache for online data, so its upgrade policy is // to simply to discard the data and start over db.execSQL(SQL_DELETE_ENTRIES); onCreate(db); } public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { onUpgrade(db, oldVersion, newVersion); } }
Чтобы получить доступ к вашей базе данных, создайте экземпляр своего подкласса SQLiteOpenHelper
:
Котлин
val dbHelper = FeedReaderDbHelper(context)
Ява
FeedReaderDbHelper dbHelper = new FeedReaderDbHelper(getContext());
Поместите информацию в базу данных
Вставьте данные в базу данных, передав объект ContentValues
методу insert()
:
Котлин
// Gets the data repository in write mode val db = dbHelper.writableDatabase // Create a new map of values, where column names are the keys val values = ContentValues().apply { put(FeedEntry.COLUMN_NAME_TITLE, title) put(FeedEntry.COLUMN_NAME_SUBTITLE, subtitle) } // Insert the new row, returning the primary key value of the new row val newRowId = db?.insert(FeedEntry.TABLE_NAME, null, values)
Ява
// Gets the data repository in write mode SQLiteDatabase db = dbHelper.getWritableDatabase(); // Create a new map of values, where column names are the keys ContentValues values = new ContentValues(); values.put(FeedEntry.COLUMN_NAME_TITLE, title); values.put(FeedEntry.COLUMN_NAME_SUBTITLE, subtitle); // Insert the new row, returning the primary key value of the new row long newRowId = db.insert(FeedEntry.TABLE_NAME, null, values);
Первый аргумент метода insert()
— это просто имя таблицы.
Второй аргумент сообщает платформе, что делать в случае, если ContentValues
пуст (т. е. вы не put
никаких значений). Если вы укажете имя столбца, платформа вставит строку и установит значение этого столбца равным нулю. Если вы укажете null
, как в этом примере кода, платформа не вставит строку, если значений нет.
Методы insert()
возвращают идентификатор вновь созданной строки или возвращают -1, если при вставке данных произошла ошибка. Это может произойти, если у вас есть конфликт с уже существующими данными в базе данных.
Чтение информации из базы данных
Для чтения из базы данных используйте метод query()
, передав ему критерии выбора и нужные столбцы. Этот метод сочетает в себе элементы insert()
и update()
, за исключением того, что список столбцов определяет данные, которые вы хотите получить («проекция»), а не данные для вставки. Результаты запроса возвращаются вам в объекте Cursor
.
Котлин
val db = dbHelper.readableDatabase // Define a projection that specifies which columns from the database // you will actually use after this query. val projection = arrayOf(BaseColumns._ID, FeedEntry.COLUMN_NAME_TITLE, FeedEntry.COLUMN_NAME_SUBTITLE) // Filter results WHERE "title" = 'My Title' val selection = "${FeedEntry.COLUMN_NAME_TITLE} = ?" val selectionArgs = arrayOf("My Title") // How you want the results sorted in the resulting Cursor val sortOrder = "${FeedEntry.COLUMN_NAME_SUBTITLE} DESC" val cursor = db.query( FeedEntry.TABLE_NAME, // The table to query projection, // The array of columns to return (pass null to get all) selection, // The columns for the WHERE clause selectionArgs, // The values for the WHERE clause null, // don't group the rows null, // don't filter by row groups sortOrder // The sort order )
Ява
SQLiteDatabase db = dbHelper.getReadableDatabase(); // Define a projection that specifies which columns from the database // you will actually use after this query. String[] projection = { BaseColumns._ID, FeedEntry.COLUMN_NAME_TITLE, FeedEntry.COLUMN_NAME_SUBTITLE }; // Filter results WHERE "title" = 'My Title' String selection = FeedEntry.COLUMN_NAME_TITLE + " = ?"; String[] selectionArgs = { "My Title" }; // How you want the results sorted in the resulting Cursor String sortOrder = FeedEntry.COLUMN_NAME_SUBTITLE + " DESC"; Cursor cursor = db.query( FeedEntry.TABLE_NAME, // The table to query projection, // The array of columns to return (pass null to get all) selection, // The columns for the WHERE clause selectionArgs, // The values for the WHERE clause null, // don't group the rows null, // don't filter by row groups sortOrder // The sort order );
Третий и четвертый аргументы ( selection
и selectionArgs
) объединяются для создания предложения WHERE. Поскольку аргументы предоставляются отдельно от запроса выбора, перед объединением они экранируются. Это делает ваши операторы выбора невосприимчивыми к SQL-инъекциям. Более подробную информацию обо всех аргументах см. в справочнике по query()
.
Чтобы просмотреть строку в курсоре, используйте один из методов перемещения Cursor
, который необходимо всегда вызывать перед началом чтения значений. Поскольку курсор начинается с позиции -1, вызов moveToNext()
помещает «позицию чтения» в первую запись в результатах и возвращает информацию о том, прошел ли курсор уже за последней записью в наборе результатов. Для каждой строки вы можете прочитать значение столбца, вызвав один из методов получения Cursor
, например getString()
или getLong()
. Для каждого метода get вы должны передать позицию индекса желаемого столбца, которую вы можете получить, вызвав getColumnIndex()
или getColumnIndexOrThrow()
. Завершив перебор результатов, вызовите функцию close()
для курсора, чтобы освободить его ресурсы. Например, ниже показано, как получить все идентификаторы элементов, хранящиеся в курсоре, и добавить их в список:
Котлин
val itemIds = mutableListOf<Long>() with(cursor) { while (moveToNext()) { val itemId = getLong(getColumnIndexOrThrow(BaseColumns._ID)) itemIds.add(itemId) } } cursor.close()
Ява
List itemIds = new ArrayList<>(); while(cursor.moveToNext()) { long itemId = cursor.getLong( cursor.getColumnIndexOrThrow(FeedEntry._ID)); itemIds.add(itemId); } cursor.close();
Удалить информацию из базы данных
Чтобы удалить строки из таблицы, вам необходимо предоставить критерии выбора, которые идентифицируют строки, для метода delete()
. Механизм работает так же, как и аргументы выбора метода query()
. Он разделяет спецификацию выбора на предложение выбора и аргументы выбора. Это предложение определяет столбцы для просмотра, а также позволяет комбинировать тесты столбцов. Аргументы — это значения для проверки, которые привязаны к предложению. Поскольку результат не обрабатывается так же, как обычный оператор SQL, он невосприимчив к SQL-инъекции.
Котлин
// Define 'where' part of query. val selection = "${FeedEntry.COLUMN_NAME_TITLE} LIKE ?" // Specify arguments in placeholder order. val selectionArgs = arrayOf("MyTitle") // Issue SQL statement. val deletedRows = db.delete(FeedEntry.TABLE_NAME, selection, selectionArgs)
Ява
// Define 'where' part of query. String selection = FeedEntry.COLUMN_NAME_TITLE + " LIKE ?"; // Specify arguments in placeholder order. String[] selectionArgs = { "MyTitle" }; // Issue SQL statement. int deletedRows = db.delete(FeedEntry.TABLE_NAME, selection, selectionArgs);
Возвращаемое значение метода delete()
указывает количество строк, которые были удалены из базы данных.
Обновить базу данных
Если вам нужно изменить подмножество значений вашей базы данных, используйте метод update()
.
Обновление таблицы объединяет синтаксис ContentValues
метода insert()
с синтаксисом WHERE
метода delete()
.
Котлин
val db = dbHelper.writableDatabase // New value for one column val title = "MyNewTitle" val values = ContentValues().apply { put(FeedEntry.COLUMN_NAME_TITLE, title) } // Which row to update, based on the title val selection = "${FeedEntry.COLUMN_NAME_TITLE} LIKE ?" val selectionArgs = arrayOf("MyOldTitle") val count = db.update( FeedEntry.TABLE_NAME, values, selection, selectionArgs)
Ява
SQLiteDatabase db = dbHelper.getWritableDatabase(); // New value for one column String title = "MyNewTitle"; ContentValues values = new ContentValues(); values.put(FeedEntry.COLUMN_NAME_TITLE, title); // Which row to update, based on the title String selection = FeedEntry.COLUMN_NAME_TITLE + " LIKE ?"; String[] selectionArgs = { "MyOldTitle" }; int count = db.update( FeedReaderDbHelper.FeedEntry.TABLE_NAME, values, selection, selectionArgs);
Возвращаемое значение метода update()
— это количество затронутых строк в базе данных.
Постоянное соединение с базой данных
Поскольку вызов getWritableDatabase()
и getReadableDatabase()
при закрытой базе данных требует больших затрат, вам следует оставлять соединение с базой данных открытым до тех пор, пока вам может понадобиться доступ к ней. Обычно оптимально закрывать базу данных в onDestroy()
вызывающего действия.
Котлин
override fun onDestroy() { dbHelper.close() super.onDestroy() }
Ява
@Override protected void onDestroy() { dbHelper.close(); super.onDestroy(); }
Отладка вашей базы данных
Android SDK включает в себя инструмент оболочки sqlite3
, который позволяет просматривать содержимое таблиц, запускать команды SQL и выполнять другие полезные функции в базах данных SQLite. Для получения дополнительной информации см. , как вводить команды оболочки .