Больше года назад я написал статью «Обзор лучшей 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