java-object-merger — больше чем просто маппер объектов

    Всем привет! Хотел бы представить вам новую библиотеку на java для маппинга/мержинга объектов, которую я “скромно” позиционирую как возможную альтернативу dozer-у. Если вы разрабатываете enterprise приложения на java, вам не безразлична эффективность вашей работы, и хочется писать меньше скучного кода, то приглашаю почитать дальше!

    UPD. Выложено в центральный репозиторий мавена
    <dependency>
            <groupId>net.sf.brunneng.jom</groupId>
            <artifactId>java-object-merger</artifactId>
            <version>0.8.5.1</version>
    </dependency>
    


    UPD2. Версия 0.8.4





    Для чего нужны мапперы объектов?

    Простой ответ: чтобы копировать данные автоматически из одного объекта в другой. Но тогда вы можете спросить: зачем нужно это копирование? Можно усомниться, что это нужно очень часто. Значит следует дать более развернутый ответ.
    В мире энтерпрайз приложений принято бить внутреннюю структуру на слои: слой доступа к базе, бизнес и представление/веб сервиса. В слое доступа к базе как правило обитают объекты мапящиеся на таблицы в базе. Условимся называть их DTO (от Data transfer object). По хорошему, они только переносят данные из таблиц и не содержат бизнес логики. На слое представления/веб сервисов, находятся объекты, доставляющие данные клиенту (браузер / клиенты веб сервисов). Назовем их VO (от View object). VO могут требовать только часть тех данных, которые есть в DTO, или же агрегировать данные из нескольких DTO. Они могут дополнительно заниматься локализацией или преобразованием данных в удобный для представления вид. Так что передавать DTO сразу на представление не совсем правильно. Так же иногда в бизнес слое выделяют бизнес объекты BO (Business object). Они являются обертками над DTO и содержат бизнес логику работы с ними: сохранение, модифицирование, бизнес операции. На фоне этого возникает задача по переносу данных между объектами из разных слоев. Скажем, замапить часть данных из DTO на VO. Или из VO на BO и потом сохранить то что получилось.

    Если решать задачу в лоб, то получается примерно такой “тупой” код:
    …
    employeeVO.setPositionName(employee.getPositionName());
    employeeVO.setPerson(new PersonVO());
    PersionVO personVO = employeeVO.getPerson();
    PersonDTD person = employee.getPerson();
    personVO.setFirstName(person.getFirstName());
    personVO.setMiddleName(person.getMiddleName());
    personVO.setLastName(person.getLastName());
    ...
    

    Знакомо? :) Если да, то могу вас обрадовать. Для этой проблемы уже придумали решение.

    Мапперы объектов

    Придуманы конечно-же не мной. Реализаций на java много. Вы можете ознакомится, к примеру тут.
    Вкратце, задача маппера — скопировать все свойства одного объекта в другой, а так же проделать все то же рекурсивно для всех чайлдовых объектов, по ходу делая необходимые преобразование типов, если это требуется.
    Мапперы из списка выше — все разные, более или менее примитивные. Самый мощный пожалуй dozer, с ним я работал около 2 лет, и некоторые вещи в нем перестали устраивать. А вялый темп дальнейшей разработки дозера побудили написать свой “велосипед” (да, я знакомился с другими мапперами — для наших требовний они еще хуже).

    Чем плох dozer

    1. Бедная поддержка конфигурирования через аннотации (есть только @Mapping).
    2. Невозможно мапить из нескольких полей в одно (к примеру собрать полное имя из имени, фамилии и отчества).
    3. Проблемы с маппингом генерик свойств. Если в родительском абстрактном классе есть геттер возвращающий генерик тип T, где , а в чайлде T определен, то при маппинге чайлда будет учитываться только спецификация типа T. Будто бы он IEntity, а не тот, которым он определен в чайлдовом классе..
      Классы свойств хранятся как строки во внутреннем кэше дозера, и чтобы получить класс, используется специальный класс лоадер. Проблемы с этим возникают в osgi окружении, когда dozer находится в одном бандле, а нужный класс бина в другом, не доступным из первого. Проблему мы побороли хоть и стандартным способом — подсунув нужный класс лоадер, но сама реализация: хранить класс в виде строки — выглядит странно. Возможно это для того чтобы не создавать perm gen space мемори ликов. Но все равно не очень понятно.
      Если что-то вдруг не мапится, то разобраться в этом очень сложно. Если вы будете дебажить дозер, то поймете почему. Там какое-то… просто сумасшедшее нагромождение ООП паттернов — все запутанно и не явно. Впрочем, это только на мой вкус.

      Какими качествами должен обладать маппер?

      1. Широкая поддержка конфигурации через аннотации.
      2. Полная поддержка генериков.
      3. Чистый, понятный код, который сможет подебажить любой не рискуя сломать мозг.
      4. По умолчанию, без каких либо дополнительных настроек, должно мапить так, как этого скорее всего будет ожидать разработчик.
      5. Должна быть возможность тонкой настройки (не хуже чем у дозера).


      Почему merger а не mapper?

      java-object-merger отличает от других мапперов одна особенность. Основополагающая идея была в том, чтобы дать возможность создавать снимки объектов (Snapshot) на некоторый момент времени, и потом, сравнивая их, находить различия (Diff) подобно тому, как находим diff между двумя текстами. Причем должна быть возможность просмотра снапшотов и диффов в понятном для человека текстовом виде. Так, чтобы раз взглянув на дифф сразу стали ясны все отличия, а так же как будет изменен целевой объект после применения диффа. Таким образом добиваемся полной прозрачности процесса. Никакой магии и черных ящиков! Создание снапшотов открывает еще один интересный сценарий. Можно сделать снапшот объекта, потом как-то изменив его, сделать новый снапшот — проверить что изменилось, и, при желании, откатить изменения. Кстати дифф можно обойти специальным visitor-ом, и пометить только те изменения, которые хочется применить, а остальные проигнорировать.
      Так что можно сказать, что merger — это нечто большее чем просто mapper.

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

      Программа “Hello world” выглядит примерно так:

      import net.sf.brunneng.jom.IMergingContext;
      import net.sf.brunneng.jom.MergingContext;
      
      public class Main {
      
         public static class A1 {
            private String field1;
      
            public String getField1() {
               return field1;
            }
      
            public void setField1(String field1) {
               this.field1 = field1;
            }
         }
      
         public static class A2 {
            private String field1;
      
            public A2(String field1) {
               this.field1 = field1;
            }
      
            public String getField1() {
               return field1;
            }
         }
      
         public static void main(String[] args) {
            IMergingContext context = new MergingContext();
            A2 a2 = new A2("Hello world!");
            A1 a1 = context.map(a2, A1.class);
            System.out.println(a1.getField1());
         }
      }
      


      Во-первых, видим, что для маппинга необходимо, чтобы у свойства был геттер на обоих объектах. Это нужно для сравнения значений. И сеттер у целевого объекта, чтобы записывать новое значение. Сами свойства должны именоваться одинаково.

      Посмотрим же как реализован метод map. Это поможет понять многие вещи о библиотеке.

      @Override
         public <T> T map(Object source, Class<T> destinationClass) {
            Snapshot sourceSnapshot = createSnapshot(source);
      
            Snapshot destSnapshot = null;
            if (sourceSnapshot.getRoot().getType().equals(DataNodeType.BEAN)) {
               Object identifier = ((BeanDataNode)sourceSnapshot.getRoot()).getIdentifier();
               if (identifier != null) {
                  destSnapshot = createSnapshot(destinationClass, identifier);
               }
            }
            if (destSnapshot == null) {
               destSnapshot = createSnapshot(destinationClass);
            }
      
            Diff diff = destSnapshot.findDiffFrom(sourceSnapshot);
            diff.apply();
            return (T)destSnapshot.getRoot().getObject();
         }
      
      


      Если исходный снапшот это бин, и если у него есть identifier, тогда пытаемся найти целевой бин для класса destinationClass используя IBeanFinder-ы [тут createSnapshot(destinationClass, identifier);]. Мы такие не регистрировали, да и identifier-а нет, значит идем дальше. В противном случает бин создается используя подходящий IObjectCreator [тут createSnapshot(destinationClass)]. Мы таких тоже не регистрировали, однако в стандартной поставке имеется создатель объектов конструктором по умолчанию — он и используется. Далее у целевого снапшота берется дифф от снапшота источника и применяется к целевому объекту. Все.

      Кстати, дифф, для этого простого случая, будет выглядеть так:
      MODIFY {
         dest object : Main$A1@28a38b58
         src object  : Main$A2@76f8d6a6
      
         ADD {
            dest property : String field1 = null
            src property  : String field1 = "Hello world!"
         }
      }
      


      Основные аннотации

      Находятся в пакете net.sf.brunneng.jom.annotations.
      • @Mapping — задает путь к полю для маппинга на другом конце ассоциации (например “employee.person.firstName”). Может быть указано на классе целевого объекта или объекта источника.
      • @Skip — поле не попадает в снапшот, не сравнивается и не мапится.
      • @Identifier — помечает поле которое считаеся идентификатором бина. Таким образом при сравнении коллекций мы будем знать какой объект с каким должен сравниваться. А именно будут сравниваться объекты с совпадающими идентификаторами. Так же, если в процессе применения диффа возникнет потребность создать бин, и при этом известен идентификатор, то будет попытка вначале найти этот бин при помощи зарегистрированных IBeanFinder-ов. Так, реализация IBeanFInder может искать бины к примеру в базе данных.
      • @MapFromMany — то же самое что и @Mapping только указывается на классе целевого объекта и позволяет указать массив свойств на объекте источнике которые будут мапится на поле в целевом объекте.
      • @Converter — позволяет задать на свойстве класс наследник PropertyConverter. — он выполнит преобразование между свойствами. Конвертер свойств обязателен при маппинге нескольких полей на одно, т.к. он как раз и должен будет собрать все значения из источника воедино и сформировать из них одно значение.
      • @OnPropertyChange, @OnBeanMappingStarted, @OnBeanMappingFinished — позволяют пометить методы прослушивающие соответствующие эвенты в жизненном цикле маппинга, которые происходят в данном бине.
      • И другие.


      Преобразования типов

      В IMergingContext можно регистрировать пользовательские преобразователи типов, из одного типа в другой (интерфейс TypeConverter). Стандартный набор преобразователей включает преобразования:
      • примитивных типов в обертки, и наоборот
      • преобразования дат
      • объектов в строку
      • энумы в энумы, и строки в энумы по имени константы энума


      Категории объектов

      Маппер разделяет все объекты на такие категории как:
      1. Объекты значения: примитивные типы, объекты в пакете java.lang, даты, массивы объектов значений. Список классов считающихся значениями можно расширять через IMergingConext.
      2. Коллекции — массивы, все наследующиеся от java.util.Collection.
      3. Мапы — все наследующиеся от java.util.Map.
      4. Бины — все остальные.


      Производительность

      Честно говоря, пока писал библиотеку — о производительности особо не задумывался. Да и изначально в целях высокой производительности не было. Однако, решил замерять время маппинга N раз на один тестовый объект. Исходный код теста. Объект довольно сложный, с полями значениями, дочерними бинами, коллекциями и мапами. Для сравнения взял dozer последней на текущий момент версии 5.4.0. Ожидал, что дозер не оставит никаких шансов. Но получилось совсем наоборот! dozer замапил 5000 тестовых объектов за 32 секунды, а java-object-merger 50000 объектов за 8 секунд. Разница какая-то дикая — в 40 раз…

      Применение

      java-object-merger был опробован на текущем проекте с моей основной работы (osgi, spring, hibernate, сотни мапящихся классов). Чтобы заменить им дозер полностью ушло менее 1 дня. По ходу находились некоторые явные косяки, но, после исправления, все основные сценарии работали нормально.

      Ленивые снапшоты

      Одна из явных проблем, найденных во время прикручивания маппера к реальному проекту было то, что если делать снапшот на DTO у которой есть ленивые списки других сущностей, а те другие ссылаются на третьи и т.д, то за создание одного снапшота можно, ненароком, выкачать пол базы. Поэтому было решено сделать все свойства в снапшоте ленивыми по умолчанию. Это означает, что они не будут вытаскиваться из объектов до момента сравнения с соответствующим свойством при взятии диффа. Или пока явно не вызовем на снапшоте метод loadLazyProperties(). А при вытаскивании свойства происходит автоматическое достраивание снапшота — опять же с ленивыми свойствами, которые ждут пока их догрузят.

      Заключение

      Если заинтересовал — проект, с исходниками и документацией находится тут . Вся основная функциональность библиотеки покрыта юнит тестами, так что можете не сомневаться в том, что каких-то глупых тривиальных ошибок вы в ней не увидите. Практически все классы и методы задокументированы javadoc-ом.
      Качайте, пробуйте, пишите свои отзывы :). Обещаю оперативно реагировать и прислушиваться к вашим пожеланиям.
    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 42

      0
      Все эти модные мапперы по большу счету выносят ошибки compile-time в runtime — что есть плохо. Это не считая дополнительных расходов на то что они пельменят/мерджат/конвертят в рантайме объекты.
      Ну, а вбить getter/setter с помощью multicursor (как в sublime text) можно за 10-30 секунд для сколько угодно количества полей (если имена getter/setter совпадают, конечно же — ну впрочем такое же условие у большенства мапперов, ну а расходы на конфигурацию маппера гораздо больше чем поменять аналогичный get/set).
        +4
        По моему опыту, в процессе написания простыней пересечевания свойств из одного объекта в другой, вероятность допустить ошибку довольно легко — забыть про какое то свойство например, засетить не то что нужно (типы случайно совпали, компилятор ничего не сказал).
        А потом искать все эти тупые ошибки. А еще попробуйте представьте как мержить коллекции бинов… В моем первом проекте было именно так, и использовать дозер мы стали не просто так. Интересно было бы узнать про сложные условия конфигурации мапперов. @Mapping аннотацию сложно поставить? Или зарегистрировать конвертер типов DateTime в Date 1 раз и забыть об этом? По поводу дополнительных расходов в рантайме, они ничтожно малы и ими можно пренебречь. Как я уже проверял 50000 операций маппинга за 8 секунд для не самого простого объекта.
          0
          Согласен с предыдущим пользователем. Имеем несколько крупных проектов EAI. А это сплошной меппинг между огромным количеством структур, в основном xml. После большого количества итераций было установлено, что тупой джавовский меппинг объектов удобнее в поддержке в 10 раз, чем все остальные способы. Пользовались и графическими мепперами, и различными фреймворками и xslt. Приходилось писать большое число тестов для мепперов (а написание теста по трудоемкости в 2 раза превышает написание самого меппера). Основные проблемы:
          1. Несовместимость объектов. Приходилось писать большое число custom-мепперов с достаточно сложной логикой, вдвойне усложненной самими ограничениями фреймворка.
          2. Не type-safe-ность. Когда сверху «спускали» новую версию интерфейсов, проводилась огромная работа по установлению какие мепперы задеты изменением.
          В итоге решение было — генерация java-объектов через JAXB с последующим ручным меппингом. Плюсы налицо: полная свобода, type-safe, простая возможность custom-логики, поддержка IDE (попапы с полями здорово помогают), повторное использование мепперов в разных структурах, моментальное отслеживание изменений. Для ускорения работы и более чистого кода используем xtend.
            0
            Приходилось писать большое число тестов для мепперов (а написание теста по трудоемкости в 2 раза превышает написание самого меппера)


            Мы слишком ленивые чтобы писать юнит тесты в текущем проекте =) Но есть одна идея как можно было бы простестить весь маппинг особо не напрягаясь… По сути, при автоматическом маппинге декларативно описывается, что куда мапить Значит будет достаточно проверить чтобы все поля замапились. И вот как это можно сделать. Допустим есть DTO и есть VO на которую мапим. 1) Заполняем все поля DTO, а так же ее чайлдов — случайными данными. Это можно сделать рефлешеном, используя тот же common-beanutils. 2) Мапим. 3) Проверям что на VO — все поля заполнены, тем же common-beanutils. Таким тестом проверим самую распространенную ошибуку, когда поля в DTO и VO случаной назвали по разному, и автоматический маппинг не сработал (ошибки он тоже не выдаст в этом случае). Если же присутствует аннотация @Mapping и поля на корое она ссылается нет — то будет ошибка во время маппинга. Таким образом можно обойтись одним (хотя и сложным) тестом.
              0
              Кстати, это навеяло добавить в java-object-merger функцию типа testMapping, которая проделате все вышеперечисленное. и выругается если какое-то поле в целевом объекте (или одном из его чайлдов) осталось незамапленное…
        +1
        Полностью согласен с предыдущим оратором (rtorsten) — object mappers, чаще всего, не привносят ничего хорошего. И добавляют множество проблем, если понадобится (а это в 99% случаев так) добавить какую-то логику конвертации, навскидку country в country code, true/false в 1/0, или любой dictionary lookup.
        Например, после пары лет эволюции в проекте дозер мэппер большой частью состоял из custom mappers, и понять весь процесс мэппинга не представлялось возможным. При переходе на тупые и деревянные plain java mappers вида a.setField(b.getField()), любой девелопер мог поддерживать меппинги. Да, выглядит ужасно. Да, копипаста. Но очень дешевые изменения и очень, очень быстрый меппинг. И этот кусок кода в проекте лежит обособленно и не мешает. И compile-time проверки.
        Любители красоты могут попробовать груви, пожертвовав быстродействием.
        Plain java подход не требует навешивания аннотаций. Это может быть критично, если доменная модель, как это любят в энтерпрайзе, генерится из xsd с помощью jaxb.
        Edit: и не забываем про final объекты, у которых вообще может не быть сеттеров.
          +1
          И добавляют множество проблем, если понадобится (а это в 99% случаев так) добавить какую-то логику конвертации, навскидку country в country code

          Может в дозере это и проблема, а в java-object-merger это можно сделать аннотацией типа: @Converter(ContryToCode.class), или конвертором типов.
          true/false в 1/0,

          Можно 1 раз зарегисрировать конвертер типов с соответсвующей логикой пребзования булевских значений в int. Не вижу никакой сложности.
          dictionary lookup.

          Аналогично, пишем конвертер вашего энума в строку и регистрируем его, если я правильно понял о чем речь.

          Проблема в ручном засечевании в том, что вызов логики конвертации у вас будет дублироваться для многих полей. А с маппером можно часто загеристрировать 1 конвертер типов один раз в одном месте, и забыть об этом — т.к. отныне он будет использоваться.

          Например, после пары лет эволюции в проекте дозер мэппер большой частью состоял из custom mappers

          Вероятно это особенность вашего проекта. У меня же был другой опыт, что очень малый процент из всех полей нуждается в какой либо конвертации. В основом это — перегнать как есть, чуть реже — настроить конвертацию на уровне типов, еще реже — осбенная конвертация на уровне отдельных полей. Поэтому утверждать о бесполезности инструмента все же не стоит. Лучше задуматься о том, как модифицировать инструмент таким образом, чтобы он оказался полезным и в вашем случае.

          Это может быть критично, если доменная модель, как это любят в энтерпрайзе, генерится из xsd с помощью jaxb.

          Если все классы учавствующие в маппинге генерируемые (такого не встречал), тогда этот инструмент конечно не подойдет. Но если хотя бы один из слоев, например слой представления, пишется вручную, то на его классах можно навесить аннотации, и маппинг будет работать в обе стороны.
            0
            Если БО генерируются в рантайме другого выхода нет.
            0
            После появления в дозере аннотации @Mapping с облегчением вздохнул… и выкинул все лишние геттеры/сеттеры из presentation- и business- объектов. Отсюда вопрос автору топика: Ваш велосипед умеет переносить внутреннее состояние между объектами, минуя геттеры/сеттеры?
              0
              Сейчас — не умеет. Для работы со свойствами используется библиотека common-beanutils. Но пожалуй в новой версии добавлю маппинг на открытые поля.
                0
                Нет-нет. Вы не совсем поняли. Речь идет не об открытых, а именно о закрытых, private полях. Проведите параллель между object/object мэппином и object/database мэппином, таким, например, как JPA.

                В JPA, к счастью, не возбраняется инициализация приватных полей сущности «напрямую». Это может на первый взгляд показаться насильственным нарушением правил инкапсуляции. Но при более внимательном рассмотрении все оказывается с точностью до наоборот. Именно способность фреймворка добраться до приватных полей напрямую позволяет нам убрать все лишние геттеры/сеттеры, а значит защитить внутреннее состояние объекта от внешнего client-кода.
                  0
                  Ах вот как… надо подумать :)
              +3
              Библиотека должна быть представлена в одном из репозиториев Maven, иначе никакие промо статьи ей не помогут.
                +1
                Конечно, сейчас как раз занимаюсь этим вопросом.
                0
                Если, скажем, IDEA не может понять куда эта библиотека копирует поле определенного обьекта, то ей решительно нельзя пользоваться как и всеми ей подобными. Со временем становится не понятно какие поля используются, какие нет, как получилось конкретное поле и тому подобное без изучения конфига меппинга. При наивном подходе нажал find usages и готово.

                И да, соглашусь с комментаторами выше — ручное копирование полей никогда не было узким местом разработки.
                  +3
                  Если, скажем, IDEA не может понять куда эта библиотека копирует поле определенного обьекта, то ей решительно нельзя пользоваться как и всеми ей подобными.

                  Как же мы тогда пользуемся? Удивительно.
                  Ребята, о чем вы спорите? А давайте отменим маппинг полей бд на сущность. Будет к примеру приходить датасет а вы с него ручками повытягиваете все поля и просетите в объект. А что… зато полный контроль. Только почему то все от этого пришли к автоматическим мапперам. Дураки наверное.
                    0
                    Да можно же запилить интеграцию иде под конкретную либу. Расширить Find Usages.
                    Разве Идея не умеет интеграции с Хибернейтом?
                    Был бы кто-нибудь из создателей проекта, которому не лень этим заниматься, и кто знает кишки двух основных IDE.
                    Не беспокойтесь так :-)

                    Чисто мое имхо — использовать нечто, не умеющее интеграцию с иде (или иде, не умеющее интеграцию с этим) — не буду, пока интеграция не появится либо я не прикручу ее самостоятельно.
                      0
                      Да можно же запилить интеграцию иде под конкретную либу. Расширить Find Usages.

                      Согласен.

                      Чисто мое имхо — использовать нечто, не умеющее интеграцию с иде (или иде, не умеющее интеграцию с этим) — не буду, пока интеграция не появится либо я не прикручу ее самостоятельно.

                      Идея вас разбаловала я вижу :) А как же написание исходного кода в блокноте, как же хардкор? :)
                        0
                        «использовать нечто, не умеющее интеграцию с иде (или иде, не умеющее интеграцию с этим) — не буду»

                        ZK framework уже давно просят добавить, а его всё нет. Но это не мешает мне кодить в IDEA с использованием оного.

                        PS: «иде» — фи.
                      0
                      А я вот пользуюсь меппером и плююсь. Можете почитать другие каменты. Многие плюются.

                      И причем тут БД? find usages для колонки вы как себе представляете? Это же сущность вне кодовой базы.
                        0
                        Так не пользуйтесь, зачем же пользоваться и плеваться :)
                          0
                          Унаследованный код. У меня по плану переписать эту часть кода.
                    –2
                    Как хорошо, что я не пишу такой enterprise где в каждом слое своя модель. Голову бы поотвинчивать тому, кто придумал всякие VO, DTO и прочую жуткую муть.
                      0
                      Если вы знаете способ как проще организовывать архитектуру enterprise, то поделитесь с общественностью. Может вы гений а мы все идиоты :)
                        0
                        Да дело не только в развесистом enterprise, иногда даже просто формирование данных сложного отчета для передачи во view требуется DTO, которое бы особым образом собрало воедино разрозненные данные. Тянуть логику непосредственно во view страх и ужас, ибо поддерживать потом такое просто невозможно.
                          0
                          Да нет. Просто стараться не плодить сущностей. В конечном итоге наши данные, это и есть наши данные, стаховые полисы, клиенты, машины, иски и т.д. Это основа всей аппликации, самый нижний слой. Ну а на нем сидит все остальное. А если потом нам нужен клиент, только вот «без такого поля», или клиентомашина — значит наверное мы делаем что — то не так и надо хорошо задуматься зачем нам это надо. Исключения это общие форматы, которые позволит передать данные дальше (XML или json).
                            +1
                            Ну если ваши данные всегда отображаются ровно так же как и сохраняются — значит вы счастливые люди. Но не увсех настолько простые проекты.
                          0
                          Имхо, это проблема самой Жавы, что элементарные вещи типа мапинга «наивным способом» реализуются откровенно плохо (тормозит, плохо рефакторится), а не наивным способом ява обрастает метаязыком на аннотациях, теряя и свою сущность, и поддержку тулзов которые умеют автоматически анализировать жаву (жаву, а не твой метаязык, черт знает как вбитый в аннотации). И то, что все «посторонние» решения типа сабжевого маппера выглядят как прикрученные сбоку костыли — нет способа модифицировать синтаксис базового языка, не потеряв интеграции с иде и другими автотулзами.
                            0
                            Заинтриговали… А а в каком языке можно реализовать маппинг изящнее?
                            элементарные вещи типа мапинга «наивным способом» реализуются откровенно плохо (тормозит, плохо рефакторится)

                            Не понял. Как раз тупое пересечевание свойств как в примере, работает супер быстро.
                            а не наивным способом ява обрастает метаязыком на аннотациях, теряя и свою сущность, и поддержку тулзов которые умеют автоматически анализировать жаву (жаву, а не твой метаязык, черт знает как вбитый в аннотации)

                            Тулзы можно научить анализировать аннотации точно так же как и обычный код, ибо аннотации это часть языка.
                            И то, что все «посторонние» решения типа сабжевого маппера выглядят как прикрученные сбоку костыли — нет способа модифицировать синтаксис базового языка, не потеряв интеграции с иде и другими автотулзами.

                            Эм… Преположим что есть плагин к ide который облегчает пользоваться некой библиотекой X версии N. Выходит версия библиотеки N + 1. Думаете если в бибиотеке нет аннотаций а только классы, то плагин не нужно будет как то менять и приспосабливать к версии N + 1?
                          +1
                          Сразу бросилось в глаза. Кликабле
                          image
                            0
                            Точно подмечено!!! Один из любимых сериалов детства :)
                              0
                              Он и сейчас вполне себе злободневен :)
                            +1
                            Добавил к себе. Вроде работает как надо, пока серьёзных проблем не нашёл. То, что надо. Спасибо автору!
                            Только от себя добавлю: вам страничка для библиотеки не помешала бы: с туториалом, с нормальной документацией и разными примерами. На английском, конечно же.
                              0
                              Спасибо! Не забудьте обновится до версии 0.8.1. Насчет туториала да, буду что-то думать… Если будут какие-то проблеммы или вопросы, пишите в Discussions на страничке проекта. Возможно туториал будет там же в Wiki.
                                +1
                                Ну вот вчера как раз сидел, разбирался. Когда нужно замаппить String name на String name, то проблем не возникает. Когда нужно замаппить что-то посложнее, а особенно из трёх полей сделать одно — то возникают непонятки. У вас в библиотеке это всё есть, да, но вот разбираться только лишь на основе исходников и обрывков JavaDoc-ов это не так то уж и лекго :) я-то сделал, что мне надо было, но это всё выглядело как блуждание в потёмках :) Так что моя просьба как от пользователя: просто приведите пару примеров с кодом из обычной жизни, где можно будет увидеть, как и в каких случаях можно использовать все аннотации. В остальном всё гуд. Спасибо ещё раз.

                                Кстати, код распространяется под Apache Licence, да? Это тоже стоит указать где-то на видном месте. Так как для больших корпораций это самый главный вопрос при выборе библиотек :)
                              0
                              message deleted
                                +1
                                В одном проекте данные из БД получали в Entity-объекты с ManyToOne сущностями, это было оправдано, т.к. «обналичивание» ссылочных сущностей с большой вероятностью обеспечивалось вторичным кешем. Далее они мапились в практически плоские сущности бизнес слоя, которые мапились на почти идентичные объекты для JAXB.
                                Мапперами стали интересоваться после того, как нахватали несколько NPE, в т.ч. с продакшена, когда вложенное свойство getA().getB().getC() сетилось в setABC() без проверки на нулл getA() и getA().getB().
                                А это уже, товарищи, чистый рантайм.

                                Один NPE поймали после рефакторинга — переноса свойства в ссылочную сущность.
                                Плюс одновременно действовали сервисы разных версий, где JAXB отличались добавлением/удалением свойств, до маппера тупо копипастили и таких копий было масса.

                                Поэтому не соглашусь, что маппер это зло, а сетфромгет это наше все.
                                  0
                                  Мы эту проблему когда-то решили переходом на groovy и, соответственно, записью вида:
                                  def some = a?.b?.c
                                  
                                    0
                                    в принципе, такие вещи должны быть покрыты тестами, независимо от того, какой способ используется.
                                      0
                                      В теории да, но на практике, юнит тесты на трансляторы, особенно если сущности большие и сложные — будут по коду в несколько раз сложнее самого транслятора (того самого тупого кода). Т.к. нужно проверить много вариантов: если большая вложнность getA().getB().getC() — 1) проверить если есть A B C, потом что A B есть, а C — null, и т.д. Да, безусловно надежность будте повышаться, но ценой долгого выпиливания юнит тестов. И если сроки поджимают будет скорее всего не до юнит тестов.
                                        0
                                        Согласен на все 100%. Там где есть человеческий фактор надо постраховываться.
                                        Я делаю тесты так заполняю объект А рандомным филером (есть масса библиотек), маплю на объект Б, потом обратно на А',
                                        затем сравниваю А и А' equals билдером, исключая непамируемые поля.

                                        Для случая с маппером можно тест автоматизировать — сделать цикл по коллекции маппируемых классов и проделать тетовые дейиствия через рефлексию

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