
SQLite – встроенная реляционная база данных, которая доступна в Android без установки сторонних компонентов. Она подходит для хранения настроек, пользовательских данных, кэша и офлайн-информации прямо внутри приложения. Работа с SQLite в Android Studio требует понимания структуры проекта, жизненного цикла базы и правил взаимодействия с SQLiteDatabase через системные API.
Подключение SQLite начинается с создания класса, наследующего SQLiteOpenHelper. В этом классе задаются имя файла базы, версия и SQL-команды для создания таблиц. Ошибки на этом этапе часто приводят к сбоям при обновлении схемы или потере данных, поэтому важно сразу продумать типы полей, первичные ключи и индексы.
В Android доступ к данным выполняется через объект SQLiteDatabase, который открывается в режиме чтения или записи. Для добавления, изменения и получения записей используются методы insert(), update(), delete() и query(). Результаты запросов возвращаются в виде Cursor, требующего корректной обработки и закрытия, чтобы избежать утечек памяти.
В статье подробно рассматривается весь процесс: от создания базы и таблиц до выполнения запросов и освобождения ресурсов. Примеры ориентированы на реальные задачи разработки Android-приложений и используют стандартные инструменты Android Studio без сторонних библиотек.
Создание класса SQLiteOpenHelper для локальной базы данных

Для работы с SQLite в Android создаётся отдельный класс, наследующий SQLiteOpenHelper. Этот класс отвечает за создание файла базы данных, обновление схемы и предоставление доступа к объекту SQLiteDatabase. Обычно его размещают в пакете data или db, чтобы изолировать логику хранения данных от UI и бизнес-кода.
В конструкторе класса указывается контекст приложения, имя файла базы данных и номер версии. Имя файла задаётся строкой, например app_data.db, и именно под этим именем база сохраняется во внутреннем хранилище приложения. Номер версии – целое число, которое увеличивается при изменении структуры таблиц.
Метод onCreate(SQLiteDatabase db) вызывается при первом обращении к базе. В нём размещаются SQL-команды CREATE TABLE. Таблицы описываются полностью: названия столбцов, типы данных, первичные ключи, ограничения NOT NULL и UNIQUE. Рекомендуется хранить SQL-строки в виде констант, чтобы упростить сопровождение кода.
Метод onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) обрабатывает изменение версии базы. Здесь выполняются команды ALTER TABLE, удаление устаревших таблиц или перенос данных во временные структуры. Простейший вариант – удалить таблицы и создать их заново, но он подходит только для данных, которые можно восстановить.
После реализации класса SQLiteOpenHelper экземпляр создаётся один раз и используется для получения базы через getWritableDatabase() или getReadableDatabase(). Такой подход гарантирует единый источник доступа к SQLite и корректное управление жизненным циклом базы данных внутри Android-приложения.
Задание версии базы и структуры таблиц через SQL

Версия базы данных задаётся целым числом при создании экземпляра SQLiteOpenHelper. Это значение используется системой для определения момента вызова onUpgrade(). При любом изменении структуры таблиц номер версии должен быть увеличен, иначе новые SQL-команды не будут применены.
Структура таблиц описывается вручную с помощью SQL-запросов CREATE TABLE. Для читаемости и контроля изменений SQL-строки обычно выносят в константы. Каждая таблица должна иметь первичный ключ, чаще всего с автоинкрементом.

После создания класса-наследника SQLiteOpenHelper база данных подключается напрямую из кода приложения. Экземпляр помощника обычно создаётся в слое работы с данными, а не в Activity, чтобы избежать повторной инициализации при смене конфигурации.
Для получения доступа используется один из двух методов: getWritableDatabase() или getReadableDatabase(). Первый открывает базу в режиме записи и чтения, второй – только для чтения, если запись недоступна из-за ошибок или ограничений файловой системы.
| Метод | Назначение | Особенности использования |
|---|---|---|
| getWritableDatabase() | Чтение и изменение данных | Создаёт базу при первом запуске, может выбросить исключение при сбое записи |
| getReadableDatabase() | Только чтение | Открывает базу в режиме read-only, если запись недоступна |
Вызов любого из методов автоматически запускает onCreate() или onUpgrade(), если файл базы отсутствует или версия отличается. Дополнительных проверок со стороны разработчика не требуется.
Полученный объект SQLiteDatabase рекомендуется хранить в одном месте и переиспользовать в пределах жизненного цикла компонента. Повторные открытия базы в разных частях кода увеличивают риск блокировок и исключений.
Закрытие базы выполняется через close() у экземпляра SQLiteOpenHelper. Делать это следует при завершении работы с данными, например при уничтожении репозитория или сервиса, чтобы освободить файловые дескрипторы и системные ресурсы.
Добавление данных в таблицы с помощью insert()

Добавление записей в SQLite выполняется через метод insert() объекта SQLiteDatabase. Перед вызовом метода данные подготавливаются в контейнере ContentValues, где каждому имени столбца соответствует конкретное значение. Такой подход исключает прямую подстановку данных в SQL-строку и снижает риск ошибок.
Экземпляр ContentValues создаётся для каждой операции вставки. В него поочерёдно добавляются пары ключ–значение с помощью методов put(), тип которых должен совпадать с типом столбца в таблице. Несоответствие типов приводит к исключениям на этапе выполнения.
Метод insert(String table, String nullColumnHack, ContentValues values) возвращает идентификатор добавленной строки. Если операция завершилась неудачно, возвращается значение -1. Этот результат следует проверять, чтобы корректно обрабатывать сбои записи.
Параметр nullColumnHack используется в редких случаях, когда вставляется пустой набор значений. Обычно передаётся null, если таблица содержит хотя бы один обязательный столбец.
После завершения вставки объект ContentValues не переиспользуется для других таблиц с иной структурой. Чёткое соответствие между набором значений и схемой таблицы упрощает отладку и поддержку кода.
Чтение и обновление записей через query() и update()

Для получения данных из SQLite применяется метод query() объекта SQLiteDatabase. Он формирует SQL-запрос на основе параметров и возвращает результат в виде Cursor. Такой способ позволяет читать данные без ручного составления строки SELECT.
В параметрах query() указываются имя таблицы, массив нужных столбцов, условие отбора и значения для подстановки. Фильтрация записей выполняется через аргументы selection и selectionArgs, где знаки вопроса заменяются фактическими значениями. Это предотвращает ошибки в синтаксисе и некорректную обработку данных.
После получения Cursor данные извлекаются построчно с помощью методов moveToFirst(), moveToNext() и чтения значений по индексу или имени столбца. Перед обращением к данным требуется проверить, содержит ли курсор хотя бы одну строку.
Обновление существующих записей выполняется через метод update(). Он принимает имя таблицы, объект ContentValues с новыми значениями и условия, определяющие, какие строки будут изменены. Отсутствие условия приведёт к обновлению всех записей в таблице.
Метод update() возвращает количество изменённых строк. Это значение удобно использовать для проверки корректности операции, например при обновлении записи по идентификатору.
После завершения чтения курсор необходимо закрыть вызовом close(). Несвоевременное освобождение курсоров приводит к утечкам ресурсов и проблемам при повторных запросах к базе данных.
Работа с Cursor и корректное закрытие ресурсов

Cursor – объект, представляющий результат запроса к SQLite. Он позволяет перемещаться по строкам таблицы и получать значения столбцов через методы getInt(), getString(), getLong() и аналогичные. Для доступа к данным можно использовать как индексы столбцов, так и их имена.
Перед чтением данных всегда проверяйте наличие записей с помощью moveToFirst(). Для обхода всех строк используют цикл с moveToNext(), что исключает пропуск строк и ошибок индексации. Любое обращение к столбцам должно учитывать их тип и возможность NULL, иначе может возникнуть исключение.
Работа с курсором всегда сопровождается выделением системных ресурсов. Для их освобождения после завершения обработки обязательно вызывается close(). Это предотвращает утечки памяти и блокировку базы данных при последующих запросах.
Если курсор используется внутри блоков try-catch, рекомендуется оборачивать вызов close() в блок finally или применять конструкцию try-with-resources. Такой подход гарантирует закрытие даже при возникновении исключений и упрощает сопровождение кода.
При работе с большими объёмами данных стоит минимизировать количество одновременно открытых курсоров и извлекать только нужные столбцы. Это снижает нагрузку на память и ускоряет выполнение операций с базой.
Вопрос-ответ:
Зачем использовать класс SQLiteOpenHelper в Android Studio?
Класс SQLiteOpenHelper упрощает управление локальной базой данных. Он отвечает за создание файла базы, контроль версии и выполнение команд SQL при обновлении схемы. Использование этого класса позволяет централизованно управлять таблицами и обеспечивать единый доступ к SQLiteDatabase без необходимости вручную проверять существование файлов или версий.
Как правильно задавать версию базы данных и зачем это нужно?
Версия базы задаётся целым числом в конструкторе SQLiteOpenHelper. Она используется системой для определения необходимости вызова метода onUpgrade() при изменении структуры таблиц. Увеличение версии при внесении изменений в схему гарантирует корректное обновление таблиц и сохранение данных.
Какая разница между getWritableDatabase() и getReadableDatabase()?
Метод getWritableDatabase() открывает базу для чтения и записи, а getReadableDatabase() — только для чтения. При сбое записи getReadableDatabase() всё равно вернёт доступ к данным. Для большинства операций добавления и изменения данных следует использовать getWritableDatabase(), для отчётного чтения или временных операций — getReadableDatabase().
Как безопасно добавлять данные в таблицы SQLite?
Для вставки данных используют метод insert() с объектом ContentValues, где каждому столбцу соответствует значение. Такой способ исключает прямое формирование SQL-строки и снижает риск ошибок. При массовом добавлении данных рекомендуется использовать транзакции, чтобы ускорить операции и предотвратить частичную запись при сбое приложения.
Что такое Cursor и как правильно с ним работать?
Cursor представляет результат запроса к базе данных и позволяет перемещаться по строкам и получать значения столбцов. Для корректной работы проверяют наличие записей с помощью moveToFirst() и обходят строки через moveToNext(). После завершения работы курсор обязательно закрывают методом close(), чтобы освободить ресурсы и избежать блокировки базы при последующих операциях.
Как правильно обновлять записи в SQLite через update() без потери данных?
Для обновления записей используют метод update() объекта SQLiteDatabase, передавая имя таблицы, объект ContentValues с новыми значениями и условие отбора строк через whereClause и whereArgs. Обязательная проверка условий предотвращает обновление всех записей случайно. После выполнения метода возвращается количество изменённых строк, что позволяет контролировать успешность операции. Если обновление затрагивает несколько таблиц или критичные поля, лучше обернуть действия в транзакцию через beginTransaction() и setTransactionSuccessful(), чтобы избежать частичной записи данных при сбое приложения.
