Управление базой данных в Android (часть 2)

В процессе работы с sqlite базой выяснилось, что не всё так просто, как казалось в [[http://blog.arkoniak.com/?p=181|Управление базой данных]]. Выяснилось несколько особенностей реализации sqlite под андроидом.

# Начиная с какой-то (видимо довольно древней) версии android, все sql запросы по умолчанию оборачиваются в транзакцию. Поэтому нельзя ставить “BEGIN TRANSACTION”, “COMMIT”, вместо этого надо использовать beginTransaction(), setTransactionSuccessful() и endTransaction(). В этом случае компилятор самостоятельно правильным образом расставит точки сохранения и откатов.
# Драйвер sqlite, как выяснилось не поддерживает множественные запросы в одной команде, то есть если будет передана последовательность команд разделённых точкой с запятой, то выполнится только самая первая. Это решать можно многими разными способами, я сделал максимально примитивно и группирую запросы в массив, по которому затем последовательно пробегаю в цикле. Возможно имеет смысл сделать какую-то более изящную конструкцию, но в принципе работает и так.

{{{lang=java
String sql_query_1 = “SOME QUERY;”;
String sql_query_2 = “SOME OTHER QUERY;”;
String[] queries = new String[]{sql_query_1, sql_query_2};

….
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
switch(oldVersion) {
case 1:
db.beginTransaction();
try{
for (String str : queries){
db.execSQL(str);
}
db.setTransactionSuccessful();
}
catch (Exception e){
// Exception processing
}
finally{
db.endTransaction();
}
}
}
}}}

Топ 10 горячих клавиш Eclipse

Следующий текст – это перевод статьи [[http://sureshkrishna.wordpress.com/2007/09/15/top-10-eclipse-shotcuts/|Top 10 eclipse hotkeys]].

Многие из тех, кто использует eclipse больше чем год привыкли к использованию горячих клавиш. Процесс запоминания всевозможных комбинаций довольно трудоёмок и конечно же нет никакой необходимости помнить их все. Ниже приведены наиболее часто используемые комбинации, которые собраны на основе достаточно длительного опыта использования в процессе разработки.

* Ctrl + Shift + O : Organize imports
* Ctrl + Shift + T : Open Type
* Ctrl + Shift + F4 : Close all Opened Editors
* Ctrl + O : Open declarations
* Ctrl + E : Open Editor
* Ctrl + / : Line Comment
* Alt + Shift + R : Rename
* Alt + Shift + L : extract to Local Variable
* Alt + Shift + M : extract to Method
* F3 : Open Declaration

Позвольте объяснить, в каких ситуациях используются эти горячие клавиши.

* Я начал разрабатывать плагин. По мере процесса разработки постоянно возникает потребность в рефакторинге кода при помощи комбинации клавиш “Alt + Shift + L” или “Alt + Shift + L” и “Alt + Shift + M“.
* В результате у меня множество методов и вероятно более 500 строчек кода в каждом. Типичный процесс разработки включает в себя комментирование и раскомментирование при помощи “Ctrl + /” (хотя существует возможность блочного комментирования по ряду причин многие разработчики предпочитают использовать “Ctrl + /”).
* По мере комментирования и раскомментирования кода, необходимо регулярно чистить import. Например классы часто копируется из одного файла в другой и поэтому удобно организовывать imports при помощи “Ctrl + Shift + O“
* Когда возникает потребность в том, чтобы найти определение переменной или метода, удобно использовать “Ctrl + O“.
* В процессе разработки, желательно видеть/проанализировать описание метода или класса, и для этого удобно нажимать “F3″.
* Пока что всё идёт хорошо и я хочу использовать интерфейс/класс и для этого ищу его через “Ctrl + Shift + T“.
* К этому моменту у нас накопилось слишком большое количество открытых редакторов и по ним удобно осуществлять навигацию при помощи “Ctrl + E“.
* И наконец, вы не выдерживаете и закрываете все окна при помощи “Ctrl + Shift + F4“.

Управление базой данных в Android

В процессе разработки с androidом и, соответственно, разработки под sqlite базу данных, возникает ряд задач. Ниже описаны сами задачи и их решения.

1. **Извлечение базы данных из устройства**
Конечно, программно управлять базой хорошо, но иногда возникает желание посмотреть как физически выглядит полученный результат. Особенно ярко это желание возникает, когда происходит апгрейд базы и соответственно требуется оценить, правильно ли он произошёл или разработчик что-нибудь забыл сделать в процессе.

Решается это с помощью утилиты adb, которая идёт в комплекте с android sdk. Находится она в директории platform-tools.
{{{lang=bash
> cd /platofrm-tools
> ./adb shell ‘cp /data/data/<название приложения, допустим com.my.app>
/databases/.db /sdcard/.db’
> ./adb pull /sdcard/.db
}}}

Тут есть несколько моментов, на которые стоит обратить внимание. Во-первых, утилита adb в принципе очень полезная, и команда adb shell запускает команду на устройстве, и в неё можно передавать обычные юниксовые команды. Например, если вы не очень понимаете, какова структура папок конкретно у вас на устройстве, то можно сделать
{{{ lang=bash
> ./adb shell ‘ls -al /data’
}}}
и дальше обычным образом просмотреть всю файловую структуру устройства, что обычно очень удобно для понимания происходящего.

Во-вторых, у меня телефон рутованый, поэтому возможно что указанная команда не будет выполнена. На форумах и stackoverflow советуют в этом случае исполнить
{{{lang=bash
> ./adb shell ‘run-as <название приложения, допустим com.my.app> cat
/data/data/<название приложения, допустим com.my.app>/databases/
.db > /sdcard/.db’
}}}

2. **Миграция баз данных**
При миграции базы возникает проблема корректного обновления старой версии базы до новой. Естественно, что если у вас база переходит с версии 1 на версию 2, то написать миграционный скрипт не сложно. Но если миграций штук десять и пользователь может перейти с любой версии на любую, то хотелось бы иметь хорошо управляемую систему, которая бы автоматически отслеживала любые возможные ситуации и применяла корректную последовательность миграций. На stackoverflow нашлось отличное решение этой проблемы. Как известно, миграция происходит при вызове метода onUpgrade, который принимает в себя номер старой и новой версии. Так вот, проблему корректных миграций решает следующая структура
{{{lang=java
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
switch(oldVersion) {
case 1:
db.execSQL(DATABASE_CREATE_SOMETHING);
// we want both updates, so no break statement here…
case 2:
db.execSQL(DATABASE_CREATE_SOMETHINGELSE);
}
}
}}}
Ключевым моментом является идея о том, что между разными case не надо ставить break! В этом случае изменения начнут применяться с правильной версии и будут накатываться до самого конца.

3. **Изменение существующих таблиц**
Помимо проблемы корректной миграции есть ещё задача правильного изменения уже существующих таблиц. В большинстве больших баз данных (типа postgre) есть масса команд вроде ALTER TABLE ALTER COLUMN, которые позволяют поменять тип колонки, добавить/удалить constraints, удалять колонки и сделать много много чего ещё. К сожалению, sqlite намного более ограничен в этом вопросе, и хотя у него есть частично подобный функционал, но он мягко говоря неудобен. Но решение существует, и общая идея состоит в том, что создаётся временная таблица, в которую копируется существующая, после чего существующая удаляется и на её месте создаётся новая с новыми правилами. В эту новую таблицу вставляются данные из бэкапа, который затем удаляется. Минус в том, что создаются промежуточные таблицы, и операции вставки занимают довольно много времени и процессорных ресурсов. С другой стороны, sqlite базы редко бывают большими, особенно на android, так что вряд ли это такая уж серьёзная проблема.
{{{lang=sql
BEGIN TRANSACTION;
CREATE TEMPORARY TABLE t1_backup(a,b);
INSERT INTO t1_backup SELECT a,b FROM t1;
DROP TABLE t1;
CREATE TABLE t1(a,b);
INSERT INTO t1 SELECT a,b FROM t1_backup;
DROP TABLE t1_backup;
COMMIT
}}}