Что в ORM тебе моем? Околонаучный подход выбора ORM для Android

    Выбор инструментов, которые так или иначе понадобятся при разработке – один из главных подготовительных этапов на старте нового Android-проекта.
    В случае, если вы разрабатываете приложение, которое должно в том или ином виде хранить большое количество сущностей – вам не избежать использования баз данных. В отличие от коллег по цеху, разрабатывающих для iOS, у Android-программистов нет удобных инструментов, облегчающих хранение объектов вроде Core Data, предоставляемых платформой (кроме Content Provider, о том почему он не в счет, будет дальше). Поэтому многие Android-разработчики прибегают к использованию сторонних ORM-решений в своих проектах. О том, на что стоит смотреть при выборе библиотеки для вашего проекта, и пойдет речь в этой статье.



    Для начала хотелось бы убедиться, что выбор ORM не надуман и мы рассмотрели все доступные средства для хранения данных, которые предоставляются Android SDK “из коробки”.

    Рассмотрим их по мере возрастания сложности написания реализации такого хранилища. Заметьте, что я специально не стал рассматривать обычные файлы в этом сравнении. Они, конечно, являются идеальным вариантом для любителей собственных велосипедов, живущих по философии Not Invented Here, но в нашем случае они будут слишком низкоуровневыми.

    Shared Preferences
    http://developer.android.com/guide/topics/data/data-storage.html#pref
    Хранилище типа ключ-значение для примитивных типов данных. Поддерживаются
    Integer, Long, Float, Boolean, String и StringSet. Основное назначение – хранение некоего состояния приложения и пользовательских настроек. По своей сути представляет обертку над XML файлом, который находится в “приватной” папке вашего приложения в поддиректории shared-prefs. Для хранения множества однотипных структурированных данных не подходит.

    Базы данных SQLite
    http://developer.android.com/guide/topics/data/data-storage.html#db
    SQLite является стандартной базой данных в Android. В фреймворке предоставлены несколько классов-помощников, облегчающих работу с базой: SQLiteOpenHelper, ContentValues и т.д. Однако, даже использование этих помощников не избавит вас от обязанности писать огромное количество шаблонного кода, самостоятельно следить за созданием и изменением таблиц, создавать методы для операций, методы для поиска и т.д. Таким образом, код приложений, использующих только стандартные инструменты для работы с SQLite в Android, становится все труднее поддерживать при добавлении новых и изменении старых сущностей.

    Content Provider
    http://developer.android.com/guide/topics/providers/content-providers.html
    Content Provider является прослойкой над реальным хранилищем данных. Может показаться, что Content Provider является “коробочной” реализацией технологии ORM, однако это далеко не так. Если вы используете SQLite в качестве хранилища для Content Provider, вам придется самостоятельно реализовать логику создания, обновления таблиц и базовых CRUD операций. В большинстве случаев использование Content Provider без специальных генераторов не только не сэкономит время на разработке и поддержке, но, возможно, и потратит его куда больше, чем написание своей реализации SQLiteOpenHelper. Однако, Content Provider позволяет использовать некоторые удобные классы платформы – такие как AsyncQueryHandler, CursorLoader, SyncAdapter и другие.

    Убеждаемся, что мы рассмотрели все доступные в Android SDK инструменты хранения данных и приходим к выводу: SQLite обеспечивает все необходимые условия для организации хранилища однотипных структурированных данных (удивительно, не правда ли?). Однако, как говорилось выше, использование SQLite в Android требует большого количества кода и постоянной поддержки, поэтому попытаемся облегчить свою жизнь, прибегнув к стороннему решению.

    Здесь на помощь как раз и приходит техника ORM — Object Relational Mapping. Ее реализация, по сути, создает впечатление объектной базы данных, имея в своей основе обычную реляционную базу данных. ORM, предоставляя более высокий уровень абстракции, призвано избавить программистов от необходимости конвертировать объекты модели данных в скалярные величины, поддерживаемые базой данных, позволить им писать меньше шаблонного кода и не беспокоиться о структуре таблиц.

    Определившись с технологией, обратимся с таким вопросом в интернет и выберем 4 библиотеки:

    Как выбрать нужную библиотеку и не пожалеть о своем решении, если будет поздно? В аналогичных статьях я наткнулся только на качественные сравнения библиотек. Однако, на мой взгляд, ORM-библиотека должна быть сбалансирована в плане удобства и производительности. Поэтому сравнение этих решений только с точки зрения API, без анализа производительности, было бы неполным. Но для начала небольшое отступление о том, почему все же стоит обращать внимание на производительность ORM.

    Зачем все это?


    Зачем оценивать производительность ORM? Очевидно же, что в конечном счете все упрется в ограничение самой SQLite, а та, в свою очередь, в ограничение файловой системы (речь идет о single-file базе данных). Однако, как выяснилось, до этих естественных ограничений еще очень далеко.

    Перед тем, как перейти к описанию проведенного мною теста и его результатам, хотелось бы рассказать небольшую историю о том, почему мы стали обращать внимание на производительность ORM, которую используем в своих проектах и попытках ее улучшить.

    Однажды к нам в Sebbia поступило на разработку некое приложение, потребляющее унифицированное для всех клиентов REST API. Из всех существующих на рынке ORM было решено использовать проверенный временем и полностью нас удовлетворяющий на тот момент ActiveAndroid. Основной сущностью приложения (для простоты назовем ее “Сущность”) является некое состояние множества других сущностей системы, части которых (“Владельцы сущности”) были представлены только идентификаторами этих сущностей. Предполагалось, что при запросе “Сущности” клиент будет загружать “Владельцев сущности” автоматически, если они не были обнаружены в кэше приложения. В мобильных устройствах такой ситуации хотелось бы избежать – в виду расходов энергии на отправку нового запроса. Какие-либо изменения в API привели бы к потенциальным проблемам совместимости с другими клиентами. Поэтому мы решили загружать и кэшировать список “Владельцев сущности” до того, как возникнет необходимость загружать сами “Сущности”. Логичнее всего такую операцию выполнять при первом запуске приложения. Стоит оговориться, что список всех “Владельцев сущности” отдавался полностью, а не постранично. Каким же было наше удивление, когда мы увидели то как долго сохраняется этот список в базу данных!

    Проверив код еще раз и убедившись, что список сохраняется один раз и внутри транзакции, под наше подозрение попал ActiveAndroid. В общем, причиной падения производительности приложения при сохранении объемного списка была рефлексия, а именно – получение значений полей объекта и заполнение ими ContentValues. Заменив код, использующий рефлексию, на код, сгенерированный самописным плагином для Eclipse, мы получили почти двухкратный прирост производительности – c ~38-ми секунд до 20-ти. Убедившись лишний раз в том, что стоит внимательнее смотреть на то, как устроены open-source библиотеки изнутри, перейдем к содержательной части статьи.

    Гонка вооружений


    Из всех выбранных библиотек особняком стоит GreenDao – ведь он единственный среди представленных решений, кто использует кодогенерацию. Чтобы не делать быстрых выводов – GreenDao быстрее всех, а про остальные забудьте, мы решили оформить подход с кодогенерацией (использованный в описанном выше проекте в ActiveAndroid) в виде отдельного форка и pull request в официальный репозиторий, параллельно дополнив его другими полезными функциями: поддержкой отношений “одно к многому”, “многое к многому” и автоматической миграции данных при смене структуры сущностей и, соответственно, их таблиц. Полученный форк, который для простоты я буду называть “ActiveAndroid.Sebbia”, был добавлен к тесту.

    Настало время рассказать о проведенном тесте. Он проверяет, как быстро та или иная библиотека может сохранять тестовые сущности в ходе SQLite транзакции и совершает обратную операцию. В новосозданную базу данных добавляются 1000 тестовых объектов, которые после очищения кэша в памяти ORM считываются и проверяются на “корректность” данных. Каждый испытуемый проверяется 10 раз, за конечный результат берется среднее время выполнения. Тестовые объекты состоят из двух текстовых полей фиксированной длины, одного поля даты и одного массива байт, полученного из сериализуемого объекта. Изначально предполагалось, что ORM должна была сама преобразовывать Serializable объект в массив байт, но оказалось, что ни у GreenDao, ни у SugarORM нет такой возможности, поэтому от этой идеи пришлось отказаться.

    Для того, чтобы понять максимально возможную скорость операции “объект-строка в таблице-объект”, которой можно достичь с помощью стандартных средств Android SDK, в сравнение был добавлен пример использующий SQLiteOpenHelper и скомпилированные SQLiteStatement. Сам проект и все библиотеки версий, на основе которых было произведено сравнение, расположен на GitHub.


    Результаты достаточно предсказуемы, No ORM-решение быстрее всех, хотя опережает оно GreenDAO совсем не на много. По большому счету, код этих решений одинаков, за исключением того, что GreenDAO предоставляет более удобный интерфейс.

    В свою очередь, GreenDAO занимает второе место в общем зачете, но первое среди ORM. И это неудивительно. GreenDAO – единственное ORM, полностью использующее кодогенерацию, скомпилированные SQLiteStatement’ы и другие оптимизации.
    На второе место среди ORM и третье в общем зачете выходит ActiveAndroid.Sebbia – наш форк ActiveAndroid, использующий кодогенерацию, работающую через Annotation Processor, и SQLiteStatement. Операция записи выполнилась почти в 4 раза быстрее по сравнению с оригинальным проектом, однако чтение удалось оптимизировать незначительно.

    ActiveAndroid в зачете ORM третий, затем следует ORMLite-ORM, пришедшая в Android-мир из “большой” Java, имеющая несколько плагинов для работы с разными источниками данных и достаточно удобная в работе. На последнем месте находится SugarORM – самая, на мой взгляд, неудачная из рассмотренных. Во первых, последняя доступная версия из master-ветки не поддерживала сохранение массива байт, пришлось исправить это недоразумение и пересобрать библиотеку, причем на GitHub’e проекта уже давно находится pull request, добавляющий это функцию. Во вторых, SugarORM создает впечатление очень сильно урезанного в функциональном плане клона ActiveAndroid (отсутствие возможности конвертировать объекты других классов и адаптеров).

    Хорошо, с производительностью разобрались – кодогенерация быстро, рефлексия медленно. Вызов SQliteDatabase.insert(...) медленнее чем вызов заранее созданного SQLiteStatement. Но насколько, все же, удобно использовать эти библиотеки? Остановимся на каждой подробнее.

    Удобства во дворе


    Начну я обзор API представленных библиотек с чемпиона – GreenDAO.
    Как уже говорилось выше, проект довольно необычен по сравнению с остальными ORM. Вместо того, чтобы создавать классы сущностей самостоятельно и указывать поля, хранящиеся в таблице, GreenDAO предлагает составлять эти классы из другого кода, полученный код генерации необходимо запускать как обычное Java приложение. Вот как примерно это выглядит:

    public static void main(String[] args) throws Exception {
    	Schema schema = new Schema(1, "com.sebbia.ormbenchmark.greendao");
    	schema.enableKeepSectionsByDefault();
    	Entity entity = schema.addEntity("GreenDaoEntity");
    	entity.implementsInterface("com.sebbia.ormbenchmark.BenchmarkEntity");
    	entity.addIdProperty().autoincrement();
    	entity.addStringProperty("field1");
    	entity.addStringProperty("field2");
    	entity.addByteArrayProperty("blobArray");
    	entity.addDateProperty("date");
    
    	new DaoGenerator().generateAll(schema, "../src-gen/");
    }
    

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

    // KEEP FIELDS - put your custom fields here
    private Blob blob;
    // KEEP FIELDS END
    

    Аналогичные блоки комментариев предусмотрены и для методов, и для импорта. Если не принимать во внимание ошеломляющую производительность такого подхода, то его удобство крайне сомнительно, особенно если использовать GreenDAO с первого дня разработки. Однако вопросы возникают и при использовании уже сгенерированного кода. Например, зачем нужно писать столько, чтобы получить DAO объект:

    DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(context, "greendao", null);
    DaoMaster daoMaster = new DaoMaster(devOpenHelper.getWritableDatabase());
    DaoSession daoSession = daoMaster.newSession();
    GreenDaoEntityDao dao = daoSession.getGreenDaoEntityDao();
    

    Мне кажется, что это чересчур. Понятно, что создавать DaoMaster каждый раз не понадобится, но все же. Итак, с GreenDAO вам придется выполнять лишние телодвижения для поддержки кода и использовать не самое удачное API. Однако взамен вы получаете скорость и приятные бонусы вроде поддержки Protobuf объектов из коробки.

    Перейдем к ORMLite. ORMLite предлагает активно использовать аннотации при объявлении своих сущностей:

    @DatabaseTable(tableName = "entity")
    public class OrmLiteEntity implements BenchmarkEntity {
    	@DatabaseField(columnName = "id", generatedId = true)
    	private long id;
    	@DatabaseField(columnName = "field1")
    	private String field1;
    	@DatabaseField(columnName = "field2")
    	private String field2;
    	@DatabaseField(columnName = "blob", dataType = DataType.BYTE_ARRAY)
    	private byte[] blobArray;
    	@DatabaseField(columnName = "date", dataType = DataType.DATE)
    	private Date date;
    }
    

    Через аннотации можно задать и тип данных поля, что очень удобно и не размазывает код, связанный с моделью по проекту. Проект поддерживает множество типов данных и вариантов их хранения. Например, для java.util.Date предусмотрен как числовой, так и строковый вариант. К недостаткам можно отнести необходимость реализовывать OrmLiteSqliteOpenHelper, через который вы сможете получить DAO объект и взаимодействовать с ORM. Использование отдельных DAO объектов избавляет от необходимости наследовать классы ваших сущностей от объектов сторонних библиотек и позволяет гибко управлять кэшем.

    ActiveAndroid использует схожий подход с аннотациями, однако требует, чтобы классы модели наследовались от предоставляемого им класса Model. На мой взгляд, такое решение оптимально по удобству только если ваши сущности уже не наследуются от какого-либо класса, родителя которого вы не можете изменить. Такое наследование позволять иметь удобные методы типа save() и delete() у объектов модели без создания дополнительных DAO объектов. В библиотеке также предоставлены сериализаторы дат BigDecimal и других типов, а для сериализации полей нестандартных типов достаточно реализовать свой TypeSerializer и указать его при инициализации.

    Как уже говорилось выше, Sugar ORM создает впечатление достаточно слабого клона ActiveAndroid. Однако Sugar ORM не требует наследования от какого-либо абстрактного класса и имеет достаточно лаконичное API:

    @Override
    public void saveEntitiesInTransaction(final List<SugarOrmEntity> entities) {
    	SugarRecord.saveInTx(entities);
    }
    
    @Override
    public List<SugarOrmEntity> loadEntities() {
    	List<SugarOrmEntity> entities = SugarRecord.listAll(SugarOrmEntity.class);
    	return entities;
    }
    

    ActiveAndroid.Sebbia представляет собой форк ActiveAndroid с поддержкой кодогенерации. В этом проекте генерация кода связывания SQLiteStatement и Cursor с объектом сущности происходит с помощью Annotation Processor. Использование Annotation Processor вместо плагина для IDE позволяет применять его как в Eclipse и в IntelliJ IDEA, так и при сборке проекта с помощью Gradle или Ant. Однако, это накладывает некоторое ограничение на видимость полей классов модели: минимальная допустимая видимость в этом случае будет без модификатора (default). Кодогенерация позволила добиться примерно 30% прироста производительности, все остальное – заслуга заранее скомпилированного SQLiteStatement. Также, этот форк содержит OneToManyRelation, ManyToManyRelation и поддержку автоматических миграций, которая используется, когда не найден SQL-скрипт миграции для текущей версии.

    Заключение


    В заключении хотелось бы подвести итог нашему небольшому исследованию. ORM – это полезный инструмент, который поможет вам сохранить время при разработке ваших приложений. И он абсолютно незаменим в случае модели с множеством сущностей и взаимосвязей между ними.
    Стоит помнить, что в реальной жизни конечный пользователь, скорее всего, не увидит никакой разницы между самым быстрым и самым медленным ORM, поэтому стоит ли задумываться на этот счет – выбор каждого. Остается добавить, что решение, которое вы выберете, должно быть удобным и отвечать вашим требованиям. В любом случае, следует придерживаться общих правил при выборе open-source библиотек в ваших проектах, а именно – оценивать, какие известные приложения используют, какое качество исходного кода и как она работает изнутри.

    Ссылки на репозитории:
    Проект-бенчмарк
    ActiveAndroid.Sebbia

    Список литературы
    1. Android Storage Options Guide
    developer.android.com/guide/topics/data/data-storage.html
    2. Object-relational mapping
    en.wikipedia.org/wiki/Object-relational_mapping
    3. Java Reflection Performance
    stackoverflow.com/q/435553/2287859
    4. Is Java Reflection Slow?
    vaskoz.wordpress.com/2013/07/15/is-java-reflection-slow
    5. Comparing android ORM libraries — GreenDAO vs Ormlite
    software-workshop.eu/content/comparing-android-orm-libraries-greendao-vs-ormlite
    6. 5 of the Best Android ORMs
    www.sitepoint.com/5-best-android-orms
    7. Android Orm Benchmark
    github.com/littleinc/android-orm-benchmark
    Sebbia
    22.43
    Company
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 32

      +2
      На этот проект не смотрели realm.io?
        –1
        Смотрели, отпугивает размер библиотеки и закрытость кода самой базы данных (доступны исходники только оберток).
          +1
          Разве закрыты? На GitHub лежат все исходники библиотеки, просто ядро на C++ (NDK).
            –2
            Исходный C++ код просмотрел мельком, смутили такие include

            #include <tightdb/group_shared.hpp>
            #include <tightdb/replication.hpp>
            #include <tightdb/commit_log.hpp>
            


            Могу ошибаться о закрытости кода ядра, создатели приводят инструкцию по сборке.
            +1
            Поддерживаю вопрос. Хотелось бы увидеть realm в отчете, для полноты картины.
            +3
            Realm — nosql база данных. Это конечно ничего, но вот отсутствие поддержки primary key меня остановило на половине пути внедрения.
              0
              github.com/realm/realm-java/pull/565
              Другой вопрос, что для боевого применения его все еще рано использовать, иногда просто падает сам движок, мы попробовали наше приложение и при наличии определенных данных движок скисал, причем не только в телефоне, но и в их браузере.
              0
              Разве это реляционная БД? Все-таки парадигма-то не одна и та же. В статье автор рассматривает использование ORM для реляционных баз данных.

              Мне гораздо интереснее было бы, если бы вы еще Cupboard попробовали и поделились своим мнением.
                +1
                Для большинства приложений реляционная БД и не нужна.
              +2
              Вот мой набор для работы с БД.
              cupboard — bitbucket.org/qbusict/cupboard
              provigen — github.com/TimotheeJeannin/ProviGen
                –4
                Я вот чего не понимаю, а зачем вообще БД? У нас же не серверное приложение, которому действительно нужно хранить много разных данных. Я писал клиенты (не под android), и ни разу не понадобилась БД. Просто хранишь все в памяти, если нужны новые данные — запрашиваешь с сервера. Это именно какая-то android-специфика?
                  +2
                  Мобильные клиенты используют БД как кэш, чтобы обеспечить работу приложения без интернета. Особенно это актуально для Android где приложение может быть выгружено из памяти в любой момент. Согласитесь, что гораздо приятнее смотреть на кэшированные данные, чем на сообщение об ошибке.
                    –1
                    Просто если это рантайм-кеш, то все равно без бд проще. Ничего лишнего, хранишь сразу как тебе удобно. А вот в условиях «приложение может быть выгружено из памяти в любой момент», действительно оптимальна локальная бд. Спасибо за разъяснения.
                      0
                      Ничего лишнего, хранишь сразу как тебе удобно.

                      Я думаю, что в основном удобно как раз в БД хранить, чем изобретать и дебажить свои велосипедные, быстро усложняющиеся со временем форматы данных, поверх которых медленно прикручиваются самодельные индексы для ускорения поиска, самопальные DQL для удобства поиска и так далее.
                      0
                      Логично поместить кешированные данные в файлы кэша.
                        +1
                        И организовать эти файлы как базу данных для удобства доступа. Например, SQLite отличное решение для хранения всего в файле… И, внезапно, стандартное для Android.
                      +1
                      Например, приложение для учета финансов с различными отчетами (без серверной части).
                      0
                      Можно попробовать настройки SQLite (cache_size и journal_mode), на андроиде их наверное можно задать через #pragma. Ускорите и чтение и запись соответственно.
                      Кроме того в SQLite insert поддерживает множественную вставку (INSERT INTO Table ( Column1, Column2 ) VALUES( Value1, Value2 ), ( Value1, Value2 )) — тоже можно оптимизировать на уровне ORM (сумеет ли андроид — другой вопрос).
                      Еще интересно, что за данные вы писали(40-20 сек это достаточно много).
                        0
                        Попробовать настройки SQLite и множественный insert действительно интересно, стоит провести эксперимент. Спасибо за совет.

                        Список о котором идет речь в статье (который сохранялся 40 секунд) состоял из 10 полей, одно из которых было ссылочным полем на другую сущность. Список состоял примерно из 500 объектов.
                          0
                          Чет тут не так. Можно код посмотреть?
                            0
                            присоединяюсь, походу коммит тут после каждого инсерта.
                              0
                              Ответил в личку
                                0
                                Код сохранения списка до оптимизации:

                                if (response.isSuccessful()) {
                                    try {
                                        ActiveAndroid.beginTransaction();
                                        ArrayList<Deputy> deputies = new ArrayList<Deputy>();
                                        JSONArray array = response.getData().getJSONArray("deputat_list");
                                        for (int i = 0; i < array.length(); ++i) {
                                            Deputy deputy = Deputy.fromJSON(array.getJSONObject(i));
                                            deputy.save();
                                            deputies.add(deputy);
                                        }
                                        this.deputies = deputies;
                                        DeputiesConnections.setRelationsF(DeputiesConnections.class, DeputiesList.this, deputies);
                                        ActiveAndroid.setTransactionSuccessful();
                                        return ERROR_NOT_SET;
                                    } catch (Exception e) {
                                        Log.e("Cannot parse deputies list", e);
                                        return ERROR_UNKNOWN;
                                    } finally {
                                        ActiveAndroid.endTransaction();
                                    }
                                }
                                


                                Код сохранения списка после введения оптимизации:

                                if (response.isSuccessful()) {
                                    try {
                                        ActiveAndroid.beginTransaction();
                                        ArrayList<Deputy> deputies = new ArrayList<Deputy>();
                                        JSONArray array = response.getData().getJSONArray("deputat_list");
                                        for (int i = 0; i < array.length(); ++i) {
                                            Deputy deputy = Deputy.fromJSON(array.getJSONObject(i));
                                            deputies.add(deputy);
                                        }
                                        Deputy.saveMultiple(deputies);
                                        this.deputies = deputies;
                                        DeputiesConnections.setRelationsF(DeputiesConnections.class, DeputiesList.this, deputies);
                                        ActiveAndroid.setTransactionSuccessful();
                                        return ERROR_NOT_SET;
                                    } catch (Exception e) {
                                        Log.e("Cannot parse deputies list", e);
                                        return ERROR_UNKNOWN;
                                    } finally {
                                        ActiveAndroid.endTransaction();
                                    }
                                }
                                


                                На самом деле точное время взято больше для красного словца, точные значения уже забылись, но порядок примерно такой.
                                  –1
                                  Скрытый текст
                                0
                                Ответил в личку
                                  0
                                  см. выше
                            0
                            На maven/jcenter планируете выложить?
                              0
                              Подскажите
                              пожалуйста сколько по времени миллисекунд выполняется коннект к базе
                              и первый запрос данных с нулевого старта приложения?

                                0
                                Большое спасибо за такое нужное приложение! Вкрячил в него свой велосипед, который, ожидаемо, оказался худшим. Буду стараться обойти хотя бы Sugar ORM.
                                  0
                                  Хм, переделал работу с транзакцией: перегнал по записи не только Sugar ORM и ActiveAndroid, но и ORMLite. Теперь вот чтение…
                                  +2
                                  Тест производительности слишком тепличный.
                                  Сам однажды наступил на подобные грабли. Делали некую ORM-прослойку на основе генерации SQL-запросов «на лету». Когда речь шла про отдельные таблицы и простейшие CRUD-запросы, все было великолепно. Когда понадобились связанные таблицы и ссылочные поля — генерация запросов усложнилась в разы (что пришлось долго отлаживать и чинить), производительность упала из-за невозможности всегда сгенерить оптимальный запрос в конкретной ситуации.
                                  Когда дошло до сложных выборок — тут объем необходимого высокоуровнего кода стал резко превышать сложность генерируемого внутри SQL-запроса. Т.е. такие задачи проще было бы решать без этой прослойки. Да вот незадача — следующий уровень абстракции уже был заточен на ORM.

                                  Но справедливости ради, думаю в Android подобных сложных кейсов быть не должно.
                                    0
                                    Когда-то наступил на грабли, решая на андроиде всё как на сервере, при работе с базой данных. Это принесло очень много неудобств и ограничений.
                                    Сейчас одна таблица аля: запрос, немного другой инфы и JSON. Результат на лицо: всё работает в разы быстрее и приятнее.
                                    Итог: на сервере подготавливать всё наперёд, а андроид — он хоть и сильный, но служит в большинстве случаев, для отображения данных.

                                  Only users with full accounts can post comments. Log in, please.