Руководство по использованию ORM DBFlow в Android-приложении

Больше года назад я написал статью «Обзор лучшей AndroidORM — ActiveAndroid«. Я бы так и продолжил использовать эту замечательную ORM, если бы несколько НО:

1) Автор забросил и библиотеку и не обновлял ее уже несколько лет, что автоматически означает отсутствие фиксов старых багов.

2) Невозможность unit-тестирования (выбрасывает исключение) и невозможность это исправить (см. пункт 1)

3) Проблемы с новыми версиями Android

4) Баги, которые я не заметил и которые могут вылезти позже, когда будет уже поздно

В связи с эти я решил сменить ORM на что-нибудь посвежее и получше. Мне советовали много разных библиотек, но я все никак не мог определиться с «самой лучшей». Изначально мой выбор пал на squidb, но от нее тоже пришлось отказаться из-за сложности ее использования. Да и кодогенерацию я не очень люблю. В конце концов я нашел ту самую библиотеку, которая мне была нужна — DBFlow.

Небольшой обзор

Одной из проблем, с существующих ORM-библиотек является то, что они используют Java-рефлексию для определения моделей баз данных, схем таблиц и отношений столбцов. DBFlow является одной из немногих библиотек, которая опирается исключительно на обработку аннотаций для создания Java-кода, основанного на базе SQLiteOpenHelper. Такой подход приводит к увеличению производительности во время выполнения, и в то же время избавляет вас от необходимости писать много шаблонного кода, как правило, необходимого для описания таблиц, выполнения запросы и т. д.

Подключение библиотеки

Первым делом нужно подключить плагин apt, прописав следующие строки в корневом build.gradle файле вашего проекта:

buildscript {
    repositories {
      // required for this library, don't use mavenCentral()
        jcenter()
    }
    dependencies {
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}

Также нам нужно прописать репозиторий jitpack.io в блоке с allprojects -> repositories:

allprojects {
    repositories {
        jcenter()
        maven { url "https://jitpack.io" }
    }
}

Далее в файле app/build.gradle активируйте плагин android-apt и добавьте DBFlow в список зависимостей. Мы создадим отдельную переменную для хранения номера версии, чтобы ее было проще изменять в будущем:

apply plugin: 'com.neenbedankt.android-apt'
def dbflow_version = "3.1.1"
dependencies {
    apt "com.github.Raizlabs.DBFlow:dbflow-processor:${dbflow_version}"
    compile "com.github.Raizlabs.DBFlow:dbflow-core:${dbflow_version}"
    compile "com.github.Raizlabs.DBFlow:dbflow:${dbflow_version}"
    // sql-cipher database encryption (optional)
    compile "com.github.Raizlabs.DBFlow:dbflow-sqlcipher:${dbflow_version}"
  }

Создание базы данных

Проаннотируйте ваш класс аннотацией @Database для объявления вашей базы данных:

@Database(name = MyDatabase.NAME, version = MyDatabase.VERSION)
public class MyDatabase {
    public static final String NAME = "MyDataBase";
    public static final int VERSION = 1;
}

Определение таблиц

Java-классы, которые выступают в качестве моделей должны наследоваться от BaseModel. Кроме того в аннотации мы должны указать базу данных, к которой относится таблица. Здесь мы покажем, как создать таблицы для организаций и пользователей:

@Table(database = MyDatabase.class)
public class Organization extends BaseModel {
  @Column
  @PrimaryKey
  int id;
  @Column
  String name;
}

Мы очень легко можем связать модели при помощи аннотации ForeignKey. Значение saveForeignKeyModel означает обновление внешнего ключа, при обновлении записи. В этом случае мы отключаем эту функцию:

@Table(database = MyDatabase.class)
public class User extends BaseModel {
    @Column
    @PrimaryKey
    int id;
    @Column
    String name;
    @Column
    @ForeignKey(saveForeignKeyModel = false)
    Organization organization;
    public void setOrganization(Organization organization) {
        this.organization = organization;
    }
    public void setName(String name) {
        this.name = name;
    }
}

Использование библиотеки Parceler

Если вместе с DBFlow вы используете библиотеку Parceler, добавьте к вакшему классу аннотацию @Parcel(analyze={...}). В ином случае библиотека Parceler попытается сериализовать поля, связанные с классом BaseModel и вызовет ошибку Error:Parceler: Unable to find read/write generator for type. Чтобы избежать этой проблемы, нужно указать, какой именно класс в цепочке наследования должны быть исследован:

@Table(database = MyDatabase.class)
@Parcel(analyze={User.class})   // add Parceler annotation here
public class User extends BaseModel {
}

Инициализация DBFlow

Далее мы должны инициализировать DBFlow в классе Application:

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        FlowManager.init(new FlowConfig.Builder(this).build());
    }
}

Измените класс приложения в файле AndroidManifest.xml:

<application
        android:name=".MyApplication"
</application>

Создание записей

Для сохранения записи в таблицу мы просто должны вызвать метод .save():

// Create organization
Organization organization = new Organization();
organization.setId(1);
organization.setName("CodePath");
organization.save();
// Create user
User user = new User();
user.setName("John Doe");
user.setOrganization(organization);
user.save();

Чтение записей

Чтение записей также производится очень легко:

List<Organization> organizationList = new Select().from(Organization.class).queryList();
List<User> users = new Select().from(User.class).where(Organization_Table.name.is("CodePath")).queryList();

Обновление записей

Повторный вызов метода save() у объекта обновит запись по первичному ключу.

Удаление записей

Для удаления записи мы должны вызвать метод delete():

user.delete();

Выполнение транзакций

Также вы можете вставить в таблицу группу элементов, используя класс ProcessModelTransaction:

ArrayList<User> users = new ArrayList<>();
// fetch users from the network
// save rows
FlowManager.getDatabase(AppDatabase.class)
          .beginTransactionAsync(new ProcessModelTransaction.Builder<>(
          new ProcessModelTransaction.ProcessModel<User>() {
              @Override
              public void processModel(User user) {
                   // do work here -- i.e. user.delete() or user.update()
                   user.save(); 
              }
          }).addAll(users).build())  // add elements (can also handle multiple)
          .error(new Transaction.Error() {
              @Override
              public void onError(Transaction transaction, Throwable error) {
              }
          })
          .success(new Transaction.Success() {
              @Override
              public void onSuccess(Transaction transaction) {
              }
          }).build().execute();

Миграции БД

Миграции определяются путем создания класса миграции или путем размещения валидного SQL-файла в папку assets/migrations/{DatabaseName}/{versionName.sql} вашего проекта. Ниже приведен пример класса миграции:

@Migration(version = {versionOfMigration}, databaseName = {DatabaseName})
public class Migration1 extends BaseMigration {
 
    @Override
    public void migrate(SQLiteDatabase database) {
 
    }
}

Исправление ошибок

Класс модели не найден

Так как DBFlow требуется от обработки аннотаций, иногда вам может понадобиться нажать Build -> New Project для генерации исходного кода.

Использование с GSON

Если вы собираетесь использовать DBFlow с библиотекой Gson , вы можете получить исключение StackOverflowError при попытке использовать объекты Java , которые наследуются от BaseModel . Для того чтобы избежать этих проблем , необходимо исключить класс ModelAdapter , который представляет собой поле в классе BaseModel:

public class DBFlowExclusionStrategy implements ExclusionStrategy {
    // Otherwise, Gson will go through base classes of DBFlow models
    // and hang forever.
    @Override
    public boolean shouldSkipField(FieldAttributes f) {
        return f.getDeclaredClass().equals(ModelAdapter.class);
    }
    @Override
    public boolean shouldSkipClass(Class<?> clazz) {
        return false;
    }
}

Затем нужно создать пользовательский Gson Builder, чтобы исключить этот класс:

GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setExclusionStrategies(new ExclusionStrategy[]{new DBFlowExclusionStrategy()});

Перейдите сюда для получения дополнительной информации.

Ссылки по теме:

Библиотека на GitHub: DBFlow

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Ваш комментарий будет опубликован после модерации