Realm на Android: разделяйте логику БД

Раньше я работал с неавтоматически управляемыми объектами модели, которые были скопированы из Realm. Я переключился на использование объекта Realm с автоматическим управлением для своих новых проектов и столкнулся с проблемой.

Если раньше я мог отделить логику БД в классах DAO, то сейчас код Realm находится во всем моем приложении.

Объекты Realm должны хорошо управляться (закрываться), везде, в каждом потоке, в каждой активности и фрагменте. Но что мне больше всего не нравится: каждый сеттер для каждого объекта модели должен быть в транзакции Realm. Прямо сейчас в моем коде есть Realm!

Кто-нибудь нашел способ отделить логику БД при использовании автоматически управляемых объектов Realm?


person Frank    schedule 27.02.2017    source источник
comment
Did anyone find a way to keep the DB logic somewhat separate, while using auto managed Realm objects? только если вы добавите синхронные обратные вызовы и завершите RealmResults в свой собственный ObservableMutableList интерфейс   -  person EpicPandaForce    schedule 27.02.2017
comment
Вы знаете пример этого где-нибудь?   -  person Frank    schedule 28.02.2017
comment
Кажется, никто никогда этого не делал, а мне было лень это делать. У меня есть только пример с использованием RxJava, где я слушаю изменения в Realm в фоновом потоке и сопоставляю/копирую данные в неизменяемые объекты данных. Плохо масштабируется с очень большими наборами данных, но, похоже, это то, что люди делают в любом случае. Но Realm не управляет этим автоматически.   -  person EpicPandaForce    schedule 28.02.2017
comment
Я написал статью о своих находках с автоматически обновляемыми объектами области, в том числе об этой проблеме. Вкратце: отделить кажется невозможным. medium.com/@ffvanderlaan/   -  person Frank    schedule 05.05.2017
comment
3.2.0 будет рассматривать переход как @Ignore. Также я думаю, что разделение не так сложно, если вы передадите Realm DAO.   -  person EpicPandaForce    schedule 05.05.2017


Ответы (2)


Вывод, после нескольких месяцев работы с автоматически управляемыми объектами Realm:

Централизованный код базы данных в классах объектов доступа к данным (DAO) и т. п. вряд ли возможен при использовании автоматически обновляемых объектов Realm.

  1. Каждый сеттер для каждого объекта модели должен находиться внутри блока транзакции Realm. Если вы используете RetroLambda, вызовы будут относительно чистыми: realm.executeTransaction(r -> user.setFirstName(firstName)); Эти блоки будут повсюду в вашем проекте в кратчайшие сроки. Если вы забудете обернуть метод установки (или вызов конструктора) в транзакцию, это приведет к сбою вашего приложения. Поверьте, это будет часто происходить в первые недели внедрения автообновляемых объектов.

  2. Автоматически обновляемые объекты не могут использоваться совместно несколькими потоками. То же самое верно и для экземпляров Realm. Все потоки, а часто и действия и фрагменты, должны будут открывать и закрывать экземпляры Realm. Вы будете постоянно думать: «На каком я треде?». Если вы пересечете границы потока с объектами модели или экземплярами Realm, вы приведете к сбою вашего приложения.

Подробнее здесь: https://medium.com/@ffvanderlaan/realm-auto-updated-objects-what-you-need-to-know-b2d769d12d76

и здесь: https://medium.com/@Zhuinden/how-to-use-realm-for-android-like-a-champ-and-how-to-tell-if-youre-doing-it-wrong-ac4f66b7f149#.gazrajqwt

person Frank    schedule 05.05.2017

Что я сделал, так это создал класс RealmController, который обрабатывает все мои транзакции Realm и извлекает новые данные из API. Скелет для реализации заимствован из другого сообщения SO, которое я не могу найти прямо сейчас, но вы сможете найти его в Google, выполнив поиск RealmController.

public class RealmController {
    private static RealmController instance;
    private final Realm realm;
    private ServiceInterface restInterface;
    private final String TAG = "RealmController";

    private static boolean browseFetchAllowed = true;

    private RealmConfiguration config = new RealmConfiguration.Builder()
            .name("myRealm")
            .schemaVersion(0)
            .deleteRealmIfMigrationNeeded()
            .build();

    public RealmController(Application application) {
        restInterface =  ServiceGenerator.createService(ServiceInterface.class);
        realm = Realm.getInstance(config);
    }

    public void setAuthentication(String token, String uid) {
        this.restInterface = ServiceGenerator.createService(ServiceInterface.class, token, uid);
    }

    public static RealmController with(Fragment fragment) {

        if (instance == null) {
            instance = new RealmController(fragment.getActivity().getApplication());
        }
        return instance;
    }

    public static RealmController with(Activity activity) {

        if (instance == null) {
            instance = new RealmController(activity.getApplication());
        }
        return instance;
    }

    public static RealmController with(Application application) {

        if (instance == null) {
            instance = new RealmController(application);
        }
        return instance;
    }

    public RealmController getInstance() {

        return instance;
    }

    public ServiceInterface getRestInterface() {
        return restInterface;
    }

    public Realm getRealm() {
        return realm;
    }
    
    public RealmResults<MyRealmObject> getStuff() {
        return realm.where(MyRealObject.class).findAll();
    }
    
    public void setStuff(RealmList<MyRealmObject> stuff) {
        realm.beginTransaction();
        realm.copyToRealmOrUpdate(stuff);
        realm.commitTransaction();
    }
    
    public void getStuffFromServer() {
        
        restInterface.getStuff().enqueue(new Callback<RealmList<MyRealmObject>>() {
            @Override
            public void onResponse(Call<RealmList<MyRealmObject>> call, Response<RealmList<MyRealmObject>> response) {
                if (response.isSuccessful()) {
                    realm.beginTransaction();
                    realm.copyToRealmOrUpdate(response.body);
                    realm.commitTransaction();
                }
            }

            @Override
            public void onFailure(Call<RealmList<RealmAd>> call, Throwable t) {
                t.printStackTrace();
            }
        });
    }
}

Внизу файла я добавил несколько примеров, но использование довольно простое:

RealmResults<MyRealmObject> results = RealmController.with(this).getStuff();

Что касается автоматического управления объектами, то я использовал RealmResults, который всегда управляется автоматически (даже если вы запрашиваете один объект). Затем просто добавьте прослушиватель изменений к результатам запроса и вуаля. Кроме того, если вы хотите автоматически обновлять данные в recyclerviews, я бы рекомендовал RealmRecyclerView.

person Juuso    schedule 27.02.2017
comment
это то, что делают мои классы DAO, они выполняют запросы. Однако некоторые из моих объектов модели имеют множество методов установки, setFirstName, setLastName и т. д. Я не хочу дублировать их все в классе DAO. Но тем не менее, эти сеттеры нужно обернуть транзакциями, и они повсюду в моем коде. - person Frank; 27.02.2017
comment
Я вижу, что вы могли бы сделать, это просто иметь общие методы установки для целых объектов -> вместо установки отдельных свойств ваших объектов иметь методы установки для всего объекта. Затем просто используйте copyToRealmOrUpdate, и область будет обрабатывать обновление полей, которые были изменены. - person Juuso; 27.02.2017
comment
Понизьте голос, потому что RealmController игнорирует ограничение потока. Кроме того, это из плохо написанного учебника Рави Тамады, который игнорирует все хорошие практики, но поддерживает плохие. Так же, как здесь; запись находится в потоке пользовательского интерфейса, - person EpicPandaForce; 27.02.2017
comment
Понижайте все, что хотите, проблема в том, что здесь используется realm.beginTransaction()... вместо realm.executeTransactionAsync()...? Если это так, то это не входит в рамки этого вопроса и приведено здесь только для примера, поэтому голосование по этой причине довольно глупо. Используются ли здесь какие-то другие плохие практики? - person Juuso; 27.02.2017
comment
Кроме того, если вы не возражаете против ссылки на учебник, вам будет интересно узнать, какие виды плохой практики он там пропагандирует. - person Juuso; 27.02.2017
comment
На первый взгляд: я не вижу вызова realm.close() в этом классе. Кроме того, это можно использовать только для однопоточного? - person Frank; 27.02.2017
comment
Технически вам даже не нужно executeTranasctionAsync(). Вы должны выполнить сетевую операцию в фоновом потоке и записать ее в Realm, который вы открываете и закрываете в фоновом потоке. Вы слушаете изменения в потоке пользовательского интерфейса, используя RealmChangeListener. Относитесь к каждому RealmResults<T> как к подписке. - person EpicPandaForce; 27.02.2017
comment
Поправьте меня, если я ошибаюсь, но afaik executeTransaction() работает в фоновом потоке, а также в Retrofit call.enqueue(...), так действительно ли необходимо запускать новый поток в дополнение к этим? Или я чего-то тут не понимаю? - person Juuso; 27.02.2017
comment
call.enqueue выполняется в фоновом потоке, а обратный вызов выполняется в потоке пользовательского интерфейса. Затем realm.executeTransaction() запускает синхронную транзакцию записи в потоке пользовательского интерфейса. Таким образом, вы можете использовать executeTransactionAsync(), за исключением того, что нет смысла переходить между bg thread =› UI thread =› bg thread =› UI thread. Что касается плохих практик в руководстве, я есть целая статья об этом. - person EpicPandaForce; 28.02.2017