Урок 39. onUpgrade. Обновляем БД в SQLite

Урок 39. onUpgrade. Обновляем БД в SQLite

С развитием приложения может возникнуть необходимость изменения структуры БД, которую оно использует. На одном из прошлых уроков я упоминал, что для этого используется метод onUpgrade класса SQLiteOpenHelper. Этот метод вызывается, если существующая версия БД отличается от той, к которой мы пытаемся подключиться. Версию мы обычно указывали при вызове конструктора супер-класса SQLiteOpenHelper в конструкторе DBHelper.

Попробуем воспользоваться методом onUpgrade и посмотреть, как происходит переход на новую версию БД. Для этого напишем небольшое приложение, аналогичное одному из приложений с прошлых уроков – про сотрудников и должности.

Первая версия БД будет содержать только таблицу people с именем сотрудника и его должностью. Но такая таблица будет не совсем корректна. Если вдруг у нас изменится название должности, придется обновлять все соответствующие записи в people. Поэтому мы решаем изменить БД и организовать данные немного по-другому.

Во второй версии добавим таблицу position с названием должности и зарплатой. И в таблице people вместо названия должности пропишем соответствующий ID из position.

Project name: P0391_SQLiteOnUpgradeDBBuild Target: Android 2.3.3Application name: SQLiteOnUpgradeDBPackage name: ru.startandroid.develop.p0391sqliteonupgradedbCreate Activity: MainActivity

Экран снова не используем, будем выводить все в лог.

Открываем MainActivity.java и кодим:

Код несложен. Я сгруппировал операции по выводу в лог данных из Cursor – метод logCursor. Метод writeStaff – выбирает данные из таблицы people и вызывает метод для вывода данных в лог. В методе Activity onCreate мы создаем объект DBHelper, подключаемся к БД, выводим в лог версию БД, вызываем writeStaff и отключаемся.

В DBHelper все как обычно. В конструкторе вызываем конструктор супер-класса. Обратите внимание, DB_VERSION = 1 – мы будем подключаться к базе версии 1. В методе onCreate создаем таблицу и заполняем ее.

Все сохраним и запустим приложение. Смотрим лог:

--- onCreate database --- --- Staff db v.1 --- Table people. 8 rows name = Иван; position = Программер; name = Марья; position = Бухгалтер; name = Петр; position = Программер; name = Антон; position = Программер; name = Даша; position = Бухгалтер; name = Борис; position = Директор; name = Костя; position = Программер; name = Игорь; position = Охранник;

БД создалась, версия = 1 и данные из таблицы вывелись в лог. Приложение работает, все ок. Но тут мы (внезапно!) понимаем, что при проектировании структуры БД была допущена ошибка. Записывать название должности в поле таблицы people – неправильно. К тому же у нас еще добавляются данные по зарплатам. Надо создать таблицу должностей - position, и использовать из нее id в таблице people. Тем самым структура нашей БД меняется и мы присваиваем ей версию – 2.

Но наше приложение уже установлено у пользователей. Оно уже создало БД версии 1, и в этой БД уже есть данные. Мы не можем просто удалить существующие таблицы и создать новые, т.к. возможно пользователь уже хранит там свои данные. Нам надо будет написать скрипты для обновления без потери данных.

План обновления такой:

- создаем и заполняем данными таблицу position- добавляем в таблицу people столбец – posid для хранения id из position- заполняем people.posid данными из position в зависимости от значения people.position- удаляем столбец people.position

Давайте менять MainActivity.java. Наше приложение теперь будет ориентировано на БД версии 2. Укажем это, изменив значение константы DB_VERSION на 2:

Метод writeStaff перепишем таким образом:

Будем выводить в лог данные из таблиц people, position и их объединения.

Реализуем метод обновления - onUpgrade в DBHelper:

Все в соответствии с планом обновления, который я приводил выше. Есть пара нюансов.

Во-первых, используем БД-транзакцию. Т.е. нам надо чтобы на БД накатились все наши обновления. А в случае ошибки в процессе обновления - все изменения должны быть отменены и БД должна остаться прежней. Тут транзакции очень выручают.

Во-вторых, в SQLite нельзя просто так удалить столбец, приходится создавать временную таблицу, перекидывать туда данные, удалять оригинал, создавать его снова с нужной структурой, скидывать в него данные из временной таблицы и удалять временную таблицу. Подробнее об этом можно почитать тут - How do I add or delete columns from an existing table in SQLite.

Наше приложение обновилось. И теперь, при запуске, оно попытается подключиться к БД версии 2, но увидит, что существующая версия = 1 и вызовет метод onUpgrade, дав нам возможность внести необходимые изменения в структуру БД. Но это произойдет в случае обновления приложения. А что будет если пользователь поставит наше новое приложение на свежий смартфон первый раз?

В этом случае приложение также попытается подключиться к БД версии 2. Но т.к. приложение только что установлено, то БД еще не существует. Приложение создаст БД и присвоит ей версию номер 2, т.к. оно умеет работать именно с такой версией. При создании будет вызван метод onCreate в DBHelper. Значит, в нем мы должны прописать код, который будет создавать нам БД версии 2 – т.е. обновленную таблицу people и новую таблицу position.

Пишем onCreate в DBHelper:

Создание и заполнение данными двух таблиц. Все понятно.

Теперь можно все сохранить и запустить приложение.

--- onUpgrade database from 1 to 2 version --- --- Staff db v.2 --- Table people. 8 rows name = Иван; posid = 2; name = Марья; posid = 3; name = Петр; posid = 2; name = Антон; posid = 2; name = Даша; posid = 3; name = Борис; posid = 1; name = Костя; posid = 2; name = Игорь; posid = 4; Table position. 4 rows name = Директор; salary = 15000; name = Программер; salary = 13000; name = Бухгалтер; salary = 10000; name = Охранник; salary = 8000; inner join. 8 rows Name = Иван; Position = Программер; Salary = 13000; Name = Марья; Position = Бухгалтер; Salary = 10000; Name = Петр; Position = Программер; Salary = 13000; Name = Антон; Position = Программер; Salary = 13000; Name = Даша; Position = Бухгалтер; Salary = 10000; Name = Борис; Position = Директор; Salary = 15000; Name = Костя; Position = Программер; Salary = 13000; Name = Игорь; Position = Охранник; Salary = 8000;

Видим, что вызывался onUpgrade и обновил нам БД с версии 1 на 2. Далее выводим все данные, чтобы убедиться, что обновление прошло корректно.

Можно также убедиться, что новый onCreate в DBHelper корректно отработает. Для этого надо удалить файл БД и запустить приложение. Приложение не найдет БД и создаст ее сразу в новом формате и с версией 2.

Сценарий выдуманный, есть к чему придраться и о чем поспорить, но смысл не в этом. Смысл в том, что мы увидели, как происходит обновление БД, если приложение запросило новую версию. Поначалу, возможно, покажется запутанным этот механизм создания и обновления. Но сложного реально ничего нет. С опытом придет полное понимание.

Еще хочу отметить, что у объекта Cursor есть метод close(), который освобождает занимаемые им ресурсы. Не забывайте про него.

Думаю, теперь можно смело сказать, что работу с SQLite в Android мы изучили достаточно основательно. И в дальнейших уроках сможем свободно использовать эти знания.

Полный код MainActivity.java:

На следующем уроке:

- разбираем как можно использовать LayoutInflater

Присоединяйтесь к нам в Telegram:

- в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.

- в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование

- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня

- новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме

📎📎📎📎📎📎📎📎📎📎