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