Sqlitedb чем открыть на андроид
Сразу предупрежу, что материал не основан на моём опыте. Несколько раз встречал описание данной задачи и решил сохранить себе на всякий случай. Буду рад любым замечаниям, если использовали подобный способ.
Upd. Прошло несколько лет, материал мог слегка устареть. Появилась ещё одна статья уже на Kotlin - Ship an Android app with a pre-populated database
Как известно, обычно мы в коде создаём новую пустую базу и пользователь начинает её заполнять. Если база небольшая, то также в коде можно программно добавить нужные записи. Но если требуется большая база данных на несколько мегабайт, то невольно задумаешься о решении проблемы.
Одним из способов является копирование заранее подготовленного файла базы данных в папку assets на этапе разработки, а далее программно можно скопировать данный файл в нужную системную папку базы данных вашего приложения. После этой операции можно работать с базой данных обычным способом.
Подготовить файл базы данных можно на рабочем компьютере при помощи различных программ для работы с SQLite, например, SQLite Database Browser.
Есть небольшая тонкость - кроме таблицы, которую вы создаёте для приложения, Android также создаёт для своих целей новую таблицу android_metadata в вашей базе. Поэтому при ручном создании базы вам необходимо создать как минимум две таблицы: системную и свою рабочую.
Откройте вашу базу и добавьте новую таблицу под названием «android_metadata»:
Добавим в таблицу одну строку:
Далее можете создать свои таблицы для работы.
Переименуйте первичное поле id на «_id», как требует Android. В SQLite Database Browser это можно сделать, нажав на кнопку Редактировать, потом выбрав таблицу, которую вы хотите изменить, и, наконец, выбрав поле для переименования.
После выполнения указанных операций база данных готова для использования в вашем приложении.
Поместите ваш файл базы данных в папку assets вашего проекта и создайте новый класс, наследующий от SQLiteOpenHelper.
Теперь вы можете создать новый экземпляр класса DataBaseHelper и вызвать методы createDataBase() и openDataBase(). Не забудьте изменить YOUR_PACKAGE на имя пакета в вашем приложении в строке DB_PATH.
Контент-провайдеры
Контент-провайдеры поддерживают стандартный синтаксис запросов для чтения, изменения, вставки и удаления данных. Если необходимо предоставить доступ к своим данных для других приложений, существует два варианта:
- создать собственный контент-провайдер, как подкласс класса ContentProvider;
- добавить данные к существующему провайдеру — если уже есть провайдер, который управляет теми же данными, и вы имеете разрешение для работы с этими данными.
rawQuery()
Также существует метод rawQuery(), принимающий сырой SQL-запрос.
Метод rawQuery() - это сырой запрос, как есть, т.е. пишется строка запроса, как это обычно делается в SQL. Пример выглядит следующим образом:
Класс SQLiteQueryBuilder - удобный способ для построения запросов. Выбор за вами!
Вставка данных для проверки
Рассмотрим, как вставлять новые данные. Добавим в меню главной активности пункт "Вставить данные". Для вставки данных применяется метод ContentValues.put(). В методе указываются ключ и значение. В качестве ключа выступает имя столбца таблицы, а его значением будет нужная информация о госте. Так как идентификатор будет вставляться автоматически, то его не используем. После того, как вы заполните все столбцы таблицы, вызывайте метод insert(), который и разместит данные в базе.
Напишем вспомогательный метод.
Вызовем метод в обработчике нажатия пункта меню.
Сразу после вставки вызываем метод displayDatabaseInfo(), чтобы увидеть результат. Можно нажимать несколько раз. Так как данные жёстко заданы в коде, то увидим одинаковые данные, кроме увеличивающего значения идентификатора.
Интерфейс
Для начала создадим интерфейс программы. Для первой активности MainActivity выберем шаблон Basic Activity. Сразу же создадим вторую активность EditorActivity из шаблона Empty Activity.
В первой активности есть кнопка Floating Action Button, через которую будем попадать на вторую активность.
Вторая активность предназначена для добавления новых гостей, которые поселяются в наш отель "Кошкин дом". Настроим его.
Экран состоит из нескольких текстовых полей и одного выпадающего списка для выбора пола гостя.
Инициализируем текстовые поля и выпадающий список.
Добавим несколько строковых ресурсов.
Метод openOrCreateDatabase: Открытие и создание баз данных без использования SQLiteOpenHelper
Вы можете открывать и создавать базы данных без помощи класса SQLiteOpenHelper, используя метод openOrCreateDatabase(), принадлежащий объекту Context вашего приложения. Получите доступ к базе данных в два шага. Сначала вызовите метод openOrCreateDatabase(), чтобы создать новую базу данных. Затем из полученного экземпляра базы данных вызовите execSQL(), чтобы выполнять команды на языке SQL, с помощью которых будут созданы таблицы и установлены отношения между ними.
Чтение данных
Считывать данные также можно двумя способами. В любом случае результат возвращается в виде объекта Cursor. Не путайте его с курсором мыши, который бегает у вас на экране.
Второй способ. Метод rawQuery()
Второй способ использует сырой (raw) SQL-запрос. Сначала формируется строка запроса и отдаётся методу rawQuery().
Запустите проект. При запуске создаётся база данных. Убедиться в этом можно, если запустить Android Device Monitor. Выберите вкладку File Explorer и найдите своё приложение (на эмуляторе). Вы увидите, что появилась папка data/data/имя_пакета/databases с файлом hotel.db. Метод getReadableDatabase создаёт или открывает базу данных.
Сейчас мы увидим, что пока у нас 0 гостей.
Небольшое предупреждение. При работе с базой данных мы обращаемся к файлу. Если база данных очень большая, то запросы не будут мгновенными. Операции с файлами являются медленными, поэтому следует использовать многопоточность. Для наших примеров это не страшно, поэтому мы пока не будем усложнять код.
Работаем с записями базы данных
Чтобы проверить работоспособность базы данных, в главной активности поместим вспомогательный метод displayDatabaseInfo() для отображения информации.
Массив projection - это список столбцов, которые нас интересуют. В SQL-запросе мы их указываем в операторе SELECT:
В методе query() третий и четвёртый параметр определяют условие WHERE. Возьмём случай с выражением:
В коде такое выражение выглядело бы так.
Как видим, в знак вопроса подставляется нужное значение.
Последний аргумент отвечает за сортировку по возрастанию или убыванию. Например, по возрасту.
Database Inspector в Android Studio
Инструмент Database Inspector позволяет исследовать базы данных, используемые в приложениях, выполнять запросы к ним, модифицировать их, делая всё это во время работы приложений. Database Inspector имеется в Android Studio начиная с версии 4.1. Этот инструмент особенно полезен при отладке механизмов программ, ответственных за работу с базами данных. Database Inspector работает и с обычной реализацией SQLite, и с библиотеками, построенными на её основе, вроде Room. Database Inspector работает лишь с библиотекой SQLite, входящей в состав операционных систем Android, уровень API которых не ниже 26.
Для того чтобы открыть базу данных в Database Inspector нужно выполнить следующие действия:
- Запустите приложение в эмуляторе или на устройстве, подключённом к компьютеру. На эмуляторе или на устройстве должно присутствовать API не ниже 26 уровня.
- Выполните команду меню View > Tool Windows > Database Inspector .
- Выберите процесс выполняющегося приложения из выпадающего меню.
- В панели Databases появятся записи о базах данных, которые имеются в выбранном приложении. Теперь можно работать с этими базами данных.
Исследование базы данных с помощью Database Inspector
Приветствую всех дроидеров в эти непростые для нас времена.
Честно говоря, заколебала эта шумиха о патентах, войнах и т.д., но в данной статье речь пойдет не об этом.
Я не собирался писать статью на данную тему, так как везде всего полно о работе с базой данных в Android и вроде бы все просто, но уж очень надоело получать репорты об ошибках, ошибках специфичных и связанных с БД.
Поэтому, я рассматрю пару моментов с которыми я столкнулся на практике, чтобы предостеречь людей, которым только предстоит с этим разбираться, а дальше жду ваших комментариев на тему решения указанных проблем после чего внесу изменения в пост и мы сделаем отличный туториал, который будет образцом работы с SQLite в Android не только для начинающих, но и для тех, кто уже знаком с основами и написал простые приложения.
Способы работы с БД
Примерно так. Более полный вариант класса и других составляющих можно посмотреть по ссылке внизу статьи.
Дополнительно можно переопределить методы onOpen(), getReadableDatabase()/getWritableDatаbase(), но обычно хватает того, что выше и методов выборки данных.
Далее, экземпляр этого класса создаем в нашем приложении при его запуске и выполняем запросы, то бишь проблемная часть пройдена. Почему она проблемная? Потому что, когда пользователь качает приложения с маркета, то не задумывается о вашей базе данных и может произойти что угодно. Скажем сеть пропала или процесс другой запустился, или вы написали уязвимый к ошибкам код.
Кстати, есть еще один момент, на который стоит обратить внимание. Переменную экземпляра нашего класса можно создать и хранить в объекте Application и обращаться по мере необходимости, но нужно не забывать вызывать метод close(), так как постоянный коннект к базе — это тяжелый ресурс. Кроме того могут быть коллизии при работе с базой из нескольких потоков.
Но есть и другой способ, например, создавать наш объект по мере необходимости обращения к БД. Думаю это вопрос предпочтения, но который также необходимо обсудить.
Вот они:
1) В методе createDataBase() строка:
SQLiteDatabase dbRead = getReadableDatabase();
и далее код… содержит crash приложения на НТС Desire, потому что получаем БД для чтения(она создается), но не закрывается.
Добавляем строкой ниже dbRead.close() и фикс готов, но момент спорный.
Вот что говорит дока на тему метода getReadableDatabase():
Create and/or open a database. This will be the same object returned by getWritableDatabase() unless some problem, such as a full disk, requires the database to be opened read-only. In that case, a read-only database object will be returned. If the problem is fixed, a future call to getWritableDatabase() may succeed, in which case the read-only database object will be closed and the read/write object will be returned in the future.
Like getWritableDatabase(), this method may take a long time to return, so you should not call it from the application main thread, including from ContentProvider.onCreate().
И так. Данный метод не стоит вызывать в главном потоке приложения. В остальном все понятно.
2) Ошибка: No such table android_metadata. Автор поста выкрутился, создав данную таблицу заранее в БД. Не знаю на сколько это правильный способ, но данная таблица создается в каждой sqlite-бд системой и содержит текущую локаль.
3) Ошибка: Unable to open database file. Здесь много мнений, разных мнений, которые Вы можете прочесть по ссылкам ниже.
Возможно, что проблемы связаны с тем, что один поток блокирует БД и второй не может к ней обратиться, возможно проблема в правах доступа к приложению(было замечено, что чаще проблемы с БД проявляются на телефонах марки НТС именно на тех моделях, которые нельзя рутануть, хотя не только на них, например на планшетах Асер), но как бы то ни было проблемы эти есть.
Я склоняюсь к варианту, что проблема в потоках, не зря ведь нам не рекомендуют вызывать методы создания базы в главном потоке.
Возможно выходом из этого будет следующее решение(рассматривается вариант №2). Используя первый вариант работы с базой, наполнить ее данными после создания, например:
Данный подход еще нужно проверить на практике, но так как этот пост нацелен на выработку верного коллективного решения по данной тематике, то комментарии и пробы на даннную тему только приветствуются.
Мораль истории такова: если вы нашли какой-то хороший кусок кода для вашего решения, то проверьте его, не поленитесь, прежде чем копипастить в свой проект.
Вцелом, данный пост показывает(касательно способа №2) как делать не надо, но и также содержит пару любопытных мыслей.
Метод getReadableDatabase() можно переопределить например так:
И класс приложения:
Отмечу, что данный класс используется только для демонстрации и проверки того, что произойдет при вызове методов getReadableDatabase()/getWritableDatabase() и создании базы. В реальных проектах код нужно адаптировать.
Кроме того в базе появилась табличка android_metadata(без моего участия), поэтому указанная выше ошибка решена.
Надеюсь кому-то пригодится.
Любопытные дополнения №1(от хабраюзера Kalobok)
Я пока совсем отказался от SQLiteOpenHelper — оказалось, что в нем невозможно создать базу на SD карте. Теоретически, то, что он возвращает, должно использоваться как путь к базе. На практике SQLiteOpenHelper иногда использует его, а иногда обходит стороной — зависит от того, открываем ли мы базу на чтение или запись, существует ли она уже и т.д. SQLiteOpenHelper.getWritableDatabase вызывает Context.openOrCreateDatabase, который, в свою очередь, использует Context.validateFilePath, чтобы получить полный путь к файлу. Там используется приватный метод Context.getDatabasesDir, переопределить который нельзя — приехали. База будет создана в стандартной директории.
SQLite доступен на любом Android-устройстве, его не нужно устанавливать отдельно.
SQLite поддерживает типы TEXT (аналог String в Java), INTEGER (аналог long в Java) и REAL (аналог double в Java). Остальные типы следует конвертировать, прежде чем сохранять в базе данных. SQLite сама по себе не проверяет типы данных, поэтому вы можете записать целое число в колонку, предназначенную для строк, и наоборот.
Вы не найдёте здесь тип, работающий с датами. Можно использовать строковые значения, например, как 2013-03-28 (28 марта 2013 года). Для даты со временем рекомендуется использовать формат 2013-03-27T07:58. В таких случаях можно использовать некоторые функции SQLite для добавления дней, установки начала месяца и т.д. Учтите, что SQLite не поддерживает часовые пояса.
Также не поддерживается тип boolean. Используйте числа 0 для false и 1 для true.
Не используйте тип BLOB для хранения данных (картинки) в Android. Лучше хранить в базе путь к изображениям, а сами изображения хранить в файловой системе.
Обратите внимание, что здесь не используется популярный в MySQL тип varchar(n), ограничивающий длину строки.
Всё, что вам нужно для начала работы с базой данных - задать необходимые настройки для создания или обновления базы данных.
Так как сама база данных SQLite представляет собой файл, то по сути при работе с базой данных, вы взаимодействуете с файлом. Поэтому операции чтения и записи могут быть довольно медленными. Настоятельно рекомендуется использовать асинхронные операции, например, при помощи класса AsyncTask.
Когда ваше приложение создаёт базу данных, она сохраняется в каталоге DATA/data/имя_пакета/databases/имя_базы.db.
Метод Environment.getDataDirectory() возвращает путь к каталогу DATA.
Основными пакетами для работы с базой данных являются android.database и android.database.sqlite.
База данных SQLite доступна только приложению, которое создаёт её. Если вы хотите дать доступ к данным другим приложениям, вы можете использовать контент-провайдеры (ContentProvider).
Курсоры
В Android запросы к базе данных возвращают объекты класса Cursor. Вместо того чтобы извлекать данные и возвращать копию значений, курсоры ссылаются на результирующий набор исходных данных. Курсоры позволяют управлять текущей позицией (строкой) в результирующем наборе данных, возвращаемом при запросе.
Класс ContentValues
Класс ContentValues используется для добавления новых строк в таблицу. Каждый объект этого класса представляет собой одну строку таблицы и выглядит как ассоциативный массив с именами столбцов и значениями, которые им соответствуют.
Вставка данных. Общая информация
Теперь разберём подробнее, как делать вставки.
Вставляем картинки в базу
Вставлять картинки в базу данных не самая лучшая идея. Оставлю только для ознакомления. Так как SQLite не работает с изображениями напрямую, то нужно сконвертировать картинку в байтовый массив. А при извлечении нужно проделать обратную задачу - из байтового массива воссоздать изображение. Создадим вспомогательный класс.
Создадим таблицу следующим образом. Переменная DATABASE_TABLE - строковая константа.
В первой части мы изучили возможности SQLite. Теперь нужно научиться подключать базу данных в приложении на Android.
SQLite зарекомендовала себя в качестве чрезвычайно надёжной системы баз данных, которая используется во многих бытовых электронных устройствах и программах, включая некоторые MP3-проигрыватели, iPhone, iPod Touch, Mozilla Firefox и др.
С помощью SQLite вы можете создавать для своего приложения независимые реляционные базы данных. Android хранит базы данных в каталоге /data/data//databases на эмуляторе, на устройстве путь может отличаться. По умолчанию все базы данных закрытые, доступ к ним могут получить только те приложения, которые их создали.
Каждая база данных состоит из двух файлов. Имя первого файла базы данных соответствует имени базы данных. Это основной файл баз данных SQLite, в нём хранятся все данные. Вы будете создавать его программно. Второй файл — файл журнала. Его имя состоит из имени базы данных и суффикса "-journal". В файле журнала хранится информация обо всех изменениях, внесенных в базу данных. Если в работе с данными возникнет проблема, Android использует данные журнала для отмены (или отката) последних изменений. Вы с ним не будете взаимодействовать, но если вы будете просматривать внутренности своего устройства, то будете знать, зачем этот файл там присутствует.
Изменение данных
Обновление не реализовано в программе, проделайте это самостоятельно.
Если запись уже существует, но вам нужно изменить какое-то значение, то вместо insert() используйте метод update(). В остальном принцип тот же. Предположим, что повторном осмотре котёнка выяснилось, что это кот, а не кошка. Если вы уже назвали котёнка Муркой, то логично назвать его теперь Мурзиком. Вызываем метод put(), а затем обновляем запись в базе данных.
Первый параметр метода update() содержит имя таблицы. Второй параметр указывает, какие значения должны использоваться для обновления. Третий параметр задает условия отбора обновляемых записей (WHERE). В приведенном примере "NAME = ?" означает, что столбец NAME должен быть равен некоторому значению. Символ ? обозначает значение столбца, которое определяется содержимым последнего параметра. Если в двух последних параметрах метода передаётся значение null, будут обновлены ВСЕ записи в таблице.
Возможны и сложные условия.
Если столбец не является строкой, то его нужно преобразовать в строку, чтобы использовать в качестве условий.
Будьте осторожны с обновлениями. Если в последних двух параметрах передать значение null, то будут обновлены все записи в таблице, так как в запросе нет условий.
Удаление данных
Также не реализовано. Проделайте самостоятельно.
Метод delete() класса SQLiteDatabase работает по тому же принципу, как и метод update(). Он имеет следующую форму:
Первый способ. ContentValues
Для вставки сначала подготавливаются данные с помощью класса ContentValues. Вы указываете имя колонки таблицы и значение для неё, т.е. работает по принципу "ключ-значение". Когда подготовите все данные во все столбцы, то вызывайте метод insert(), который сразу раскидает данные по столбцам.
Способ очень удобен, требует мало кода и легко читаем. Вы создаёте экземпляр класса, а затем с помощью метода put() записываете в нужную колонку нужные данные. После чего вызывается метод insert(), который помещает подготовленные данные в таблицу.
У метода insert() три аргумента. В первом указывается имя таблицы, в которую будут добавляться записи. В третьем указывается объект ContentValues, созданный ранее. Второй аргумент используется для указания колонки. SQL не позволяет вставлять пустую запись, и если будет использоваться пустой ContentValue, то укажите во втором аргументе null во избежание ошибки.
Метод query()
Для чтения данных используют вызов метода query():
В метод query() передают семь параметров. Если какой-то параметр для запроса вас не интересует, то оставляете null:
- table — имя таблицы, к которой передается запрос;
- String[] columnNames — список имен возвращаемых полей (массив). При передаче null возвращаются все столбцы;
- String whereClause — параметр, формирующий выражение WHERE (исключая сам оператор WHERE). Значение null возвращает все строки. Например: _id = 19 and summary = ?
- String[] selectionArgs — значения аргументов фильтра. Вы можете включить ? в "whereClause"". Подставляется в запрос из заданного массива;
- String[] groupBy - фильтр для группировки, формирующий выражение GROUP BY (исключая сам оператор GROUP BY). Если GROUP BY не нужен, передается null;
- String[] having — фильтр для группировки, формирующий выражение HAVING (исключая сам оператор HAVING). Если не нужен, передается null;
- String[] orderBy — параметр, формирующий выражение ORDER BY (исключая сам оператор ORDER BY). При сортировке по умолчанию передается null.
Объект Cursor, возвращаемый методом query(), обеспечивает доступ к набору записей результирующей выборки. Для обработки возвращаемых данных объект Cursor имеет набор методов для чтения каждого типа данных — getString(), getInt() и getFloat().
Чтобы получить все записи из нужных столбцов без условий, достаточно указать имя таблицы в первом параметре и строчный массив во втором. В остальных параметрах оставляем null:
Для запроса с условием используются третий и четвёртый параметр. Например, нам нужны записи, у которого имя кота будет Барсик:
Параметр "NAME = ?" означает, что столбец NAME должен содержать значение, указанное следующем параметре.
Множественные условия. Об этом говорилось выше.
Пример при использовании числовых значений, например, идентификатор первичного ключа (об этом тоже говорилось)
Для сортировки используется последний параметр. Нужно указать нужный столбец, по которому будет проводиться сортировка. По алфавиту с буквы A - ASC, наоборот - DESC
Возможна сортировка по нескольким столбцам.
Вы можете использовать функции SQL для составления запросов: AVG(), COUNT(), SUM(), MAX(), MIN() и др.
Ещё один абстрактный пример подсчёта средней суммы:
Внедрение опасного кода
При работе с базой данной надо следить за безопасностью данных. Опытный пользователь может удалить базу. На своём устройстве он делать этого может и не будет, но на устройстве жертвы вполне.
Простой вариант атаки. Допустим у нас есть поле для ввода идентификатора, чтобы узнать информацию о госте. Нормальный пользователь введёт число "3" для поиска третьего гостя. В коде это будет следующим образом.
Тогда идентификатор будет _ID == 2;.
Хакер может ввести следующую строку в текстовое поле:
В коде это превратится в следующее:
Таким образом вредитель внедрил нежелательный код, который удалит таблицу.
Подписываем контракт
Теперь можно заняться интеграцией базы данных в приложение.
При работе с базой данных принято создавать новый пакет data внутри основного пакета. Щёлкаем правой кнопкой мыши по имени пакета, выбираем New | Package и вводим новое имя.
В последних рекомендациях Гугла рекомендуется создавать класс-контракт. Будем придерживаться этого правила. Мы как бы подписываем контракт на работу с базой данных и предоставляем все нужные данные.
Внутри созданного пакета создаём новый класс HotelContract. Класс-контракт является контейнером для базы данных и может содержать несколько внутренних классов, которые представляют отдельные таблицы (не забывайте, что база данных может содержать несколько таблиц). Внутри класса создаём внутренний класс. В нашем случае будет один класс для таблицы guests.
Нам следует задать схему таблицы и константы для столбцов для удобства. Класс будет выглядеть так.
В классе используется реализация интерфейса BaseColumn:
Что это нам даёт? В большинстве случаев работа с базой данных происходит через специальные объекты Cursor, которые требуют наличия в таблице колонки с именем _id. Вы можете создать столбец вручную в коде, а можно положиться на BaseColumn, который создаст столбец с нужным именем автоматически. Дело ваше. Если вы не будете работать с курсорами, то можете использовать и стандартное наименование id или вообще не использовать данный столбец, но не советую так поступать, чтобы не вырабатывать вредных привычек.
После создания класса мы можем изменить код в EditorActivity в том месте, где происходит выбор пола гостя через выпадающий список.
Метод update()
Для обновления и удаления записей в базе данных используют соответственно методы update() и delete():
В этих методах два последних параметра формируют SQL-выражение WHERE аналогично рассмотренному методу query() для чтения данных. Эти методы возвращают число модифицированных или удаленных строк.
Обновление строк также происходит с помощью класса ContentValues. Создайте новый объект ContentValues, используя метод put() для вставки значений в каждый столбец, который вы хотите обновить. Вызовите метод update() в контексте базы данных, передайте ему имя таблицы, обновленный объект ContentValues и оператор WHERE, указывающий на строку (строки), которую нужно обновить.
В первом параметре метода мы указываем имя таблицы, которую хотим обновить.
Во втором параметре мы указываем значение, которое хотим обновить. Например, в колонке "DESCRIPTION" нужно обновить значение на "Clever".
В третьем параметре указывается условие для обновления. Выражение "NAME = ?" означает, что колонка NAME должна быть равна указанному значению. Знак вопроса является подстановочным символом и из четвёртого параметра берётся нужное значение.
Обратите внимание, что последний параметр является массивом. Вы можете использовать множественные условия для запроса. Например, нам нужно обновить запись в таблице, используя сразу две колонки для составления условия.
Данный код соответствует выражению Where NAME = "Murzik" or DESCRIPTION = "Nice".
Для условий всегда используются строки, поэтому может понадобиться конвертация в некоторых случаях. Например, нужно использовать идентификатор таблицы со значением 1.
Если в последних двух параметрах использовать null, то будут обновлены все записи в таблице.
Библиотека для работы с готовой базой
На Github есть проект в виде отдельной библиотеки, позволяющая упростить работу по переносу готовой базы данных с компьютера на устройство. Как я понял из описания, базу нужно заархивировать и положить в папку assets/databases/.
Метод insert()
Для вставки новой записи в базу данных SQLite используется метод insert():
В метод insert() необходимо передать три параметра:
- table — имя таблицы, в которую будет вставлена запись;
- nullColumnHack — в базе данных SQLite не разрешается вставлять полностью пустую строку, и если строка, полученная от клиента контент-провайдера, будет пустой, то только этому столбцу явно будет назначено значение null;
- values — карта отображений (класс Map и его наследники), передаваемая клиентом контент-провайдера, которая содержит пары ключ-значение. Ключи в карте должны быть названиями столбцов таблицы, значения — вставляемыми данными.
Метод insert() возвращает идентификатор _id вставленной строки или -1 в случае ошибки.
Для создания новой строки понадобится объект ContentValues, точнее, его метод put(), чтобы обеспечить данными каждый столбец. Объект ContentValues представляет собой пару name/value данных. Вставьте новую строку, передавая в метод insert(), вызванный в контексте нужной нам базы данных, имя таблицы и объект ContentValues
Наполняем базу данных
Создадим вспомогательный метод для вставки записи в базу данных. Для этого считываем данные, которые вводятся в текстовые поля, а далее по предыдущему учебному примеру.
Метод вызывается в меню для значка с галочкой, которая выводится на панели действия активности.
Запускаем проект и проверяем работу кода.
▍Метод onCreate()
Метод onCreate() вызывается при создании базы данных. Он, в процессе жизненного цикла приложения, вызывается лишь один раз. А именно, его вызов производится при первом обращении к методу getReadableDatabase() или getWritableDatabase() . Эти методы принадлежат классу SQLiteOpenHelper .
В следующем примере показано создание экземпляра класса DatabaseHelper , являющегося наследником SQLiteOpenHelper :
Вот код конструктора DatabaseHelper :
Класс SQLiteOpenHelper вызывает метод onCreate() после создания базы данных и создания экземпляра класса SQLiteDatabase . Этот метод, напомним, вызывается лишь один раз, при создании базы данных:
Классы для работы с SQLite
Работа с SQLite напрямую теперь считается устаревшим подходом. На Google I/O 2017 был представлен новых механизм доступа к базе данных через специальную обёртку Room
Работа с базой данных сводится к следующим задачам:
- Создание и открытие базы данных
- Создание таблицы
- Создание интерфейса для вставки данных (insert)
- Создание интерфейса для выполнения запросов (выборка данных)
- Закрытие базы данных
Запускаем SQLite на эмуляторе
С помощью утилиты ADB можно запустить SQLite на эмуляторе и работать с базами данных напрямую.
Напомню, что можно активировать команду sqlitе3 для одной из перечисленных баз данных, введя следующую команду:
Для завершения работы с sqlite3 напишите:
Чтобы просмотреть список таблиц:
Быстрый доступ к главной таблице:
Таблица sqlite_master - это главная таблица (master table), в которой отслеживаются таблицы и виды, содержащиеся в базе данных. Следующая команда распечатывает инструкцию create для таблицы people, находящейся в базе данных contacts.db:
Это один из способов, позволяющих узнать названия всех столбцов, которые содержатся в таблице базы данных. Можно скопировать базу данных на локальный компьютер и изучать её в более комфортных условиях. Чтобы переместить файл contacts.db, можно дать следующую команду:
Что такое SQLite?
SQLite — это опенсорсная реляционная СУБД, похожая на MySQL. SQLite входит в состав стандартной библиотеки Android, где реализован движок базы данных, не нуждающийся в применении клиент-серверной архитектуры, не требующий особой настройки, поддерживающий транзакции. Для работы SQLite не нужен сервер баз данных. Всё, что нужно, хранится в обычных файлах. Полноценная БД SQLite, в которой имеется множество таблиц, триггеров, индексов и представлений, содержится в единственном, самом обыкновенном файле. Стандартная поддержка СУБД SQLite имеется во всех мобильных телефонах и в большинстве компьютеров. При этом работа с SQLite не требует решения задач по администрированию или настройке баз данных.
Android-разработчик, для выполнения CRUD-операций из своего приложения, может пользоваться пакетом android.database.sqlite.SQLiteDatabase , в котором реализованы все необходимые API.
▍Метод onUpgrade()
Метод onUpgrade() вызывается в тех случаях, когда нужно обновить версию существующей базы данных:
SQLiteOpenHelper
Следующий шаг - создание класса в пакете data, который наследуется от специального класса SQLiteOpenHelper и непосредственно работает с базой данных. В классе создаются константы для удобной работы. Также реализуются методы onCreate() и onUpgrade().
Созданный класс будет работать с базой данных - добавлять, выбирать, удалять записи и прочие операции.
Напомню, как выглядит схема нашей таблицы.
Щёлкаем правой кнопкой мыши на имени пакета в левой части студии и выбираем в меню New | Java Class и в диалоговом окне выбираем имя для нового класса, например, HotelDbHelper. Слово Helper обычно используют, чтобы показать, что класс является обёрткой (вспомогательным классом) какого-то абстрактного класса. Впрочем, вы можете придумать более замысловатое название, например, ILoveNewYork или CatsForever. Спустя год, когда вы вернётесь к своему примеру, это будет так увлекательно вспоминать, для чего был создан класс с таким красивым именем.
У нас появится заготовка. Наследуемся от SQLiteOpenHelper. Студия предложит создать два обязательных метода onCreate() и onUpgrade(), о которых поговорим позже.
После добавления методов студия по-прежнему ругается. Теперь ему подавай конструкторы. Получится такой код.:
Класс HotelDbHelper.
Третий параметр null в суперклассе используется для работы с курсорами. Сейчас их не используем, поэтому оставим в покое.
Как вы уже догадались, константа DATABASE_NAME отвечает за имя файла, в котором будет храниться база данных приложения. Можно придумать любое имя и обойтись без расширения. Но мне так привычнее.
Вторая константа DATABASE_VERSION требует дополнительных объяснений. Она отвечает за номер версии базы. Принцип её работы схож с номером версий самого приложения. Когда мы видим, что вышла новая версия Chrome 33, то понимаем, что пора обновляться. Аналогично поступает и само приложение, когда замечает, что номер версии базы стал другим. Как только программа заметила обновление номера базы, она запускает метод onUpgrade(), который у нас сформировался автоматически. В этом методе необходимо разместить код, который должен сработать при обновлении базы.
Метод onCreate() вопросов не вызывает - здесь создаётся сама база данных с необходимыми данными для работы.
Метод вызывается, если в устройстве нет базы данных и наш класс должен создать его. Как мы помним, у метода есть параметр db, который относится к классу SQLiteDatabase. У класса есть специальный метод execSQL(), которому нужно передать запрос (SQL-скрипт) для создания таблицы. Для создания таблицы в SQL используется команда CREATE TABLE . . Для удобства вынесем команду в отдельную строку. Аналогично поступим с командой DROP TABLE. Так как строка очень длинная и состоит из множества строковых переменных, которые нужно соединить в одну цепочку, то поступают следующим образом. Создаём ещё одну строковую константу для формирования скрипта и передадим её в метод.
Основная сложность - не пропустить пробелы в запросе. Очень часто пропущенный пробел становится источником проблем и ваше приложение не может создать таблицу. Можете сначала написать сам скрипт создания таблицы, а уже потом заменять отдельные слова константами. Идентификатор _id всегда должен использовать INTEGER PRIMARY KEY AUTOINCREMENT, остальные колонки на ваше усмотрение.
Теперь нужно объяснить, зачем нужен этот метод onUpgrade(). Представьте ситуацию, что вы первоначально создали в базе таблицу, в которую заносятся имена котов и их электронные адреса и телефоны (продвинутые кошаки). Вроде всё замечательно. Если нужно поздравить усатых-полосатых с Международным днём кошек, который отмечается 1 марта, то проблем нет никаких. У вас есть список имён, по которому вы можете пройтись и лично написать каждому письмо. Пользователи, скачавшие ваше приложение, с удовольствием заполняют базу данных и дружно пишут письма мелким почерком. И вдруг до вас дошло, что совершили непростительную ошибку. Вы забыли добавить в базу данных даты рождения котов. А значит их никто не поздравит и не погладит (((.
Вы исправляете досадное упущение и выкладываете новую версию программы в открытый доступ. Новые пользователи, которые установят программу первый раз, радуются жизни - у них есть все необходимые данные для работы. Но что делать тем, кто уже работает со старой программой? Обновившись, они увидят дополнительное текстовое поле для ввода даты рождения, но в старой базе нет колонки для хранения новых данных. И ваша программа завершится с ошибкой. Полностью удалять и устанавливать новую версию программы тоже не выход - тогда пропадут старые данные, что тоже не желательно. Для таких случаев вы пишете код в методе onUpgrade(), чтобы при обновлении поменялась структура базы данных у старых пользователей. Мы позже попробуем смоделировать эту ситуацию.
Итак, метод onUpgrade() вызывается при несовпадении версий. Часто в этом методе просто удаляют существующую таблицу и заменяют её на новую. Это самое простое и практичное решение. Впрочем, на первых порах, вам вряд ли придётся заниматься подобными делами, поэтому метод можно оставить даже пустым.
Когда ваше приложение будет готово, то в папке data/data/имя_пакета/databases появится файл hotel.db (позже я вам покажу). Этот файл и будет вашей базой данных, в которой будет находиться созданная вами таблица. На данный момент в студии нет готового плагина для просмотра таблиц (в Eclipse есть), но вроде уже видел плагин от сторонних разработчиков. А пока вам придётся скачивать из устройства файл базы данных и просматривать его на компьютере специальными программами, работающими с SQLite на локальном компьютере.
Другой способ
Ещё один способ, найденный в сети.
Поместим файл базы данных в zip-архив и переместим его в папку res/raw.
В методе onCreate() проверим - запускается ли приложение первый раз. Распакуем БД и переместим её на SD-карту. Установим соединение с базой. Проверим, существует ли файл БД, если не существует, то нужно его развернуть из файла.
Автор статьи, перевод которой мы публикуем сегодня, хочет рассказать об использовании баз данных SQLite в Android-разработке. В частности — он коснётся тут двух вопросов. Во-первых — речь пойдёт о классе SQLiteOpenHelper , который применяется для работы с базами данных в коде приложений. Во-вторых — он уделит определённое внимание инструменту Database Inspector, инспектору баз данных, встроенному в Android Studio.
Класс SQLiteOpenHelper
SQLiteOpenHelper — это класс, встроенный в пакет android.database.sqlite.SQLiteDatabase . Это — вспомогательный класс, который отвечает за создание баз данных SQLite и за управление их версиями. Для того чтобы воспользоваться возможностями этого класса, нужно создать его подкласс, в котором надо переопределить два метода — onCreate() и onUpgrade() . Этот класс позволяет открывать существующие базы данных, создавать новые базы данных и обновлять версии баз данных.
Второй способ. SQL-запрос
Существует также другой способ вставки через метод execSQL(), когда подготавливается нужная строка и запускается скрипт. Этот способ возможно понравится PHP-кодерам, которые привыкли к такому синтаксису.
Научившись вставлять данные, можно заняться второй активностью, которая и предназначена для этих целей.
Метод delete()
Чтобы удалить строку, просто вызовите метод delete() в контексте базы данных, указав имя таблицы и оператор WHERE. В результате вы получите строки, которые хотите удалить:
Кроме вышеперечисленных методов, в этом классе также определены различные методы для выполнения других общих задач управления базой данных.
Класс SQLiteOpenHelper: Создание базы данных
Библиотека Android содержит абстрактный класс SQLiteOpenHelper, с помощью которого можно создавать, открывать и обновлять базы данных. Это основной класс, с которым вам придётся работать в своих проектах. При реализации этого вспомогательного класса от вас скрывается логика, на основе которой принимается решение о создании или обновлении базы данных перед ее открытием. Класс SQLiteOpenHelper содержит два обязательных абстрактных метода:
- onCreate() — вызывается при первом создании базы данных
- onUpgrade() — вызывается при модификации базы данных
Также используются другие методы класса:
- onDowngrade(SQLiteDatabase, int, int)
- onOpen(SQLiteDatabase)
- getReadableDatabase()
- getWritableDatabase()
В приложении необходимо создать собственный класс, наследуемый от SQLiteOpenHelper. В этом классе необходимо реализовать указанные обязательные методы, описав в них логику создания и модификации вашей базы.
В этом же классе принято объявлять открытые строковые константы для названия таблиц и полей создаваемой базы данных, которые клиенты могут использовать для определения столбцов при выполнении запросов к базе данных. Например, так можно объявить константы для таблицы CONTACT:
Лучше, если вы будете давать сразу понятные имена, указывающие на работу с таблицей. Если имя переменной TABLE_NAME ещё можно оставить, то для других лучше использовать более говорящие имена, например, KEY_NAME и KEY_PHONE или COLUMN_NAME, COLUMN_PHONE.
Ваш класс, расширяющий SQLiteOpenHelper, также неявно наследует интерфейс BaseColumns, в котором определена строковая константа _ID, представляющая имя поля для идентификаторов записей. В создаваемых таблицах базы данных поле _ID должно иметь тип INTEGER PRIMARY KEY AUTOINCREMENT. Описатель AUTOINCREMENT является необязательным. Часто в других примерах идентификатор создаётся вручную, смотрите как вам удобнее. Только всегда называйте его именно _id. Такое название используется в Android для работы с курсорами и поэтому придерживайтесь данного правила.
В методе onCreate() необходимо реализовать логику создания таблиц и при необходимости заполнить их начальными данными при помощи SQL-команды, например:
Здесь показана условная команда, в реальном коде вам нужно учитывать пробелы между константами, чтобы получилась правильная строка SQL-команды.
Также надо указать номер версии базы данных, начиная со значения 1. По этому номеру класс-обёртка будет понимать, что структуру базы данных следует обновить.
В методе onUpgrade() можно, например, реализовать запрос в базу данных на уничтожение таблицы (DROP TABLE), после чего вновь вызвать метод onCreate() для создания версии таблицы с обновленной структурой, например, так:
В параметрах метода используются номера версий базы данных - старая и новая. О номере версии говорилось выше.
Обновляется структура базы данных следующим образом. Сначала меняем порядковый номер.
При первой установке приложения базы данных ещё не существует. Тут проверять пока нечего. При установке новой версии приложения система проверит номер версии базы данных. Если он больше, чем было, то вызовется метод onUpgrade(). Если наоборот, то вызовется метод onDowngrade() (обычно так происходит редко).
Как использовать? Просто ставим условие на проверку.
Допустим, в таблицу нужно добавить новую колонку в дополнение к существующим колонкам NAME, DESCRIPTION и IMAGE_RESOURCE_ID. Для изменения таблицы используется выражение SQL с ключевым словом ALTER
Для замены имени таблицы используется выражение (меняем имя таблицы DOG на CAT):
Для удаления таблицы:
Соответственно, нужно вызвать метод SQLiteDatabase.execSQL(), передав ему нужную команду SQL.
Для удобства создадим вспомогательный метод updateDatabase().
Созданный метод теперь можно использовать в коде следующим образом.
Чтобы использовать реализацию вспомогательного класса, создайте новый экземпляр, передайте его конструктору контекст, имя базы данных, текущую версию и объект класса CursorFactory (если вы его используете). Вызовите метод getReadableDatabase() или getWritableDatabase(), чтобы открыть и вернуть экземпляр базы данных, с которой мы имеем дело (он будет доступен как для чтения, так и для записи). Вызов метода getWritableDatabase() может завершиться неудачно из-за проблем с полномочиями или нехваткой места на диске, поэтому лучше предусмотреть откат к методу getReadableDatabase().
Если база данных не существует, вспомогательный класс вызывает свой обработчик onCreate(); если версия базы данных изменилась, вызовется обработчик onUpgrade(). В любом случае вызов методов getWritableDatabase/getReadableDatabase, в зависимости от ситуации, вернет существующую, только что созданную или обновленную базу данных.
Оба метода имеют разные названия, но возвращают один и тот же объект. Только метод для чтения getReadableDatabase() сначала проверит доступность на чтение/запись. В случае ошибки метод проверит доступность на чтение и только потом уже вызовет исключение. Второй метод сразу проверяет доступ к чтению/записи и вызывает исключение при отказе в доступе.
Если вы планируете использовать несколько таблиц, то рекомендуется создавать для каждой таблицы отдельный класс.
SQLiteDatabase
Для управления базой данных SQLite существует класс SQLiteDatabase. В классе SQLiteDatabase определены методы query(), insert(), delete() и update() для чтения, добавления, удаления, изменения данных. Кроме того, метод execSQL() позволяет выполнять любой допустимый код на языке SQL применимо к таблицам базы данных, если вы хотите провести эти (или любые другие) операции вручную.
Каждый раз, когда вы редактируете очередное значение из базы данных, нужно вызывать метод refreshQuery() для всех курсоров, которые имеют отношение к редактируемой таблице.
Для составления запроса используются два метода: rawQuery() и query(), а также класс SQLiteQueryBuilder.
▍Пример
Создадим простое Android-приложение и разберём практический пример работы с классом SQLiteOpenHelper . Это приложение, применяя данный класс, позволит нам добавлять записи о работниках некоей компании в таблицу Empdata базы данных Company.db .
Приложение имеет весьма простой интерфейс, описанный в файле activity_main.xml .
Интерфейс приложения
Вот содержимое файла MainActivity.java :
Вот содержимое Employee.java :
Вот файл DatabaseHelper.java :
В коде DatabaseHelper.java видно, что класс DatabaseHelper является наследником класса SQLiteOpenHelper .
После того, как база данных создана, можно приступать к работе с ней. В частности — создавать и обновлять поля её таблиц. При решении этих задач можно пользоваться объектом типа ContentValues , который позволяет хранить пары ключ-значение. Именно такие объекты используются для добавления новых записей в таблицы базы данных (с помощью метода insert() объекта SQLiteDatabase ) и для обновления существующих записей (с помощью метода update() ).
Первый способ. Метод query()
Извлечение данных происходит через метод query(). Данные хранятся в наборе строк, которые можно представить в виде таблицы. Из этой таблицы вы уже можете извлечь конкретное значение.
У метода query() множество параметров. В первом параметре укажите имя таблицы, во втором - массив имён колонок, далее идут дополнительные условия. Пока везде оставим null. В нашем примере мы добавили одну запись и извлечь её просто.
Читайте также: