MyBatis и Hibernate на одном проекте. Как подружить?

 
Добрый день, я - Алексей Зиновьев [Alexey Zinoviev], человек, который решил подружить MyBatis и Hibernate на одном проекте.

Жил я себе, жил, в ус не дул, но встретился на моем пути мне один проект.

 

Лапша JDBC в коде.

Около года назад мне достался специфический проект странной архитектуры. В нем было несколько баз данных. Была одна главная база данных, доступная на чтение от стороннего производителя, схему которой поменять было нельзя. Было несколько побочных баз, где хранились дополнительные данные, которые хранить хотелось, да в основной базе кололось, т.к. она была readonly. Задачей проекта было получение разнообразных аналитических отчетов с экспоненциальным от числа отчетов числа настроек.

Из главной базы данные читались при помощи JDBC и для каждого семейства настроек существовал отдельный запрос в базу данных.


Данные в побочных базах управлялись при помощи Hibernate и власть фреймворка не была распространена на все приложение: оно ютилось на задах и обеспечивало небольшую нишу своими объектами на уровне CRUD - операций.


По основной базе не были сгенерированы объекты, ибо отчеты были много сложнее, чем те объекты, которые Hibernate мог сгенерировать на основе по - страшному нормализованной БД. Объекты могли получится уродливыми и лишенными смысла. Данные приходилось денормализовывать и представлять на более понятном уровне.


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


Все это хозяйство было сложно поддерживать. Изменения требований влекли изменения во многих местах кода. Добавление нового отчета было нетривиальной творческой задачей с большим бюджетом.


Надо было это переломить, выделить слои, обеспечить простой механизм добавления отчета.


Необходимы были нужны нормальные иерархии объектов, а также простой способ превращать объекты в записи таблиц БД.


Ищем ORM? Давайте разбираться.




Hibernate

О том, что такое Hibernate и с чем его едят, можно подробнее узнать  здесь (почти на русском) и здесь (официальный сайт).

Разработчику необходимо думать в терминах объектов. Нужно наследование, композиция, т.е. старая добрая объектная модель хранения снаружи и реляционная по сути. Т.е. там где раньше были таблицы и foreign keys теперь должны появиться объекты, отношения и ссылки на другие объекты. Hibernate не заменит разработчику DAO - слой, но существенно облегчит жизнь.


При помощи простейших манипуляций вы сможете либо генерировать DDL инструкции для СУБД, создавая схему БД по классам, либо сможете создавать классы по схеме БД (техника Reverse Engineering ). Я чаще всего пользуюсь второй возможностью, ибо мне удобно проектировать в терминах БД.

К особенностям стоит отнести:

  • накопление очереди SQL- запросов и массированное выполнение (впрочем есть flush);
  • 3 - уровневый кэш;
  • HQL - собственный язык запросов;
  • возможность использования аннотаций или map - файлов.
  • возможность вызова родного диалекта SQL;
  • удобное постраничное получение данных; 
  • распознает рефлексию (связь один-ко-многим к своей таблице) при Reverse Engineering; 
  • настройка каскадного удаления;
  • Criteria API для недругов SQL.  
Советую установить плагин для Eclipse позволяющий немного порезвиться с Hibernate.



MyBatis (iBatis в девичестве)

Об этой ORM люди знают меньше, книжек еще меньше, а толковых примеров внедрения, так и днем с огнем не сыскать.

Отличное стартовое руководство поможет вам сделать свой MyBatis - helloworld.


К особенностям стоит отнести:
  • конфигурирование в коде или в специальном файле;
  • удобные псевдонимы для имен классов;
  • корректное разделение и хранение ваших SQL по произвольному числу особенных файлов;
  • динамическая безопасная сборка SQL (целых секций) в зависимости от параметров, позволяет сократить число запросов;
  • SQL просто отлаживать;
  • Вы видите ошибки SQL отдельно от ошибок Java; 
  • возможность использования аннотаций или map - файлов. Причем аннотации менее популярны. 


Плагин для Eclipse слегка облегчит вам жизнь по редактированию запросов.

Дабы рассуждать не голословно, рассмотрим два популярных application - case [ типа приложений ].

 

Application - case №1 "Мы начинаем с нуля"

Вы начали новый проект, вдохновенно пишите запросы на золотом родном JDBC - style, лихо копируете методы, меняя только текст запросов для передачи в PreparedStatement, схема еще не ясна, вы готовите первый прототип для заказчика и у вас "небольшая" иерархия сущностей стадом в 100 голов.

На этом этапе важно уменьшить стоимость изменения кода вслед за изменением схемы базы данных. 

Требования к  программе:

  • незначительные изменения в коде при незначительных изменениях в схеме;
  • массированное редактирование данных;
  • много записи и мало чтения (данные еще не накопили).
Так начиналось много проектов.

В принципе, можно сразу прикручивать Hibernate для простых грустных объектов POJO (самые грустные объекты на свете).

 

Вы уже неделю разрабатываете проект?

У вас сто методов в суперъобъекте  MyDAO, в каждом из которых вы лезете в базу и, обходя записи в цикле, выкладываете это все в массив строк для последующей конвертации?

Ваш аналитик прислал вам новые уточнения схемы БД, из - за которой надо переписать добрую половину из этих ста процедур?


Ваш тестер сыплет в вас багами с ошибками СУБД о некорректной сборке запроса, SQL - инъекциях?

Не переживайте, это можно побороть, но для этого надо найти время на глубокий рефакторинг вашего DAO. Если он у вас, конечно, есть..

Да, пока не поздно, выделите объекты вместо убогих списков списков строк, чисел и прочих оболочек примитивов. А потом по объектам создавайте таблицы. 




Application - case №2 "У нас есть готовая БД"

Типичная ситуация: у вас уже есть БД, схема которой устоялась, доставшаяся по наследству, в ней лежат бесценные данные, а вот софт морально устарел или вам необходимо написать Web - версию вашего настольного приложения. 

Данные и их агрегация тут выходят на передний план.


Требования к  программе:

  • сбор аналитики, построение отчетов;
  • незначительное редактирование данных;
  • много записи и чтения.
Ситуация коренным образом отличается от app case #1. Таблицы, скорее всего, будут неплохо в такой 3 или 2 нормальной форме и с этим придется жить. 
Хм. Hibernate конечно стоит попробовать. Причем только Reverse Engineering. Создать объекты по таблицам. Но возможно объекты будут не те, что нужно. Совсем не те. По опыту скажу, что объекты по ним будут уродливые и неполноценные. Просто нагенерировать таблицы - плохой выход.
А где хранить SQL для отчетов?

Или собирать строки отчетов в коде из объектов? 

А сущности, которые вы будете получать - это будут колонки отчетов? Т.е. получать колонки отчета как коллекции? А если они рассинхронизируются за это время?
А сколько вариантов придется написать, если заказчику нужны настройки этого отчета?
 
Тут на помощь могут прийти вызов родного SQL в Hibernate - методах, HQL.
Но насколько это удобно поддерживать? И насколько быстро это будет? 

Но полностью переводить общение с БД на MyBatis довольно нудновато, если есть много объектов, для которых нужно реализовать CRUD - операции. Да и настроек для поддержки стандарта JPA у него будет поменьше, чем у Hibernate.

Лично для меня app case #2 являлся вызовом и задачей, которую необходимо было решать.  

Был выбран путь по разделу сфер влияния между двумя ORM.

Шаги по внедрению

Следует начать с того, чтобы промаркировать таблицы данных следующим образом:
  • READONLY - контент, который поставляется нам со стороны;
  • REPORT - таблицы, поставляющие данные для аналитической отчетности;
  • ENTITY -  удобные для понимания сущности, которые нужно будет редактировать; 
  • PART OF ENTITY <ENTITY_NAME> - часть удобной сущности ( в скобках указана сущность).
Все таблицы с меткой ENTITY можно прогнать сквозь мясорубку Reverse Engeneering, создав по ним классы - сущности, бины, оформленные по удобному стандарту. Они прекрасно будут жить и на вашем сервере, принимать данные из Controller и отдавать их для редактирования. Но не более.
Далее, для сущностей распавшихся по таблицам, с меткой PART OF ENTITY <ENTITY_NAME>, можно применить два подхода (тут ваша свобода выбора) : 
  • мы пишем запрос, кладем его в MyBatis, а затем выгружаем его результат в некий целостный объект, замапленный при помощи MyBatis;
  • мы руками создаем классы по стандарту JPA или пишем map - файл для Hibernate сами, или создаем foreign keys (если это возможно в БД) и полагаемся на Reverse Engeneering.
Для сущностей с меткой READONLY и REPORT стоит написать запрос, который на выходе получает список строк отчета, которые маппятся в коллекцию (List) объектов типа "строка отчета". Тут слой DAO связан с архитектурной идеей построения отчетов, которая может быть иной в иных приложениях.
Впрочем могут быть таблицы с меткой READONLY, важные для нашего приложения, с ними поступаем также, как и в предыдущем пункте, с отличием в том, что выгрузка будет в другие коллекции.
В итоге мы получаем множество объектов, так или иначе связанных с таблицами БД, а также прототипы первичных множеств строк для отчетов.
  

Архитектура, сферы ответственности

Мы получили приложение с четко поделенными сферами ответственности. Часть сущностей - редактируемые пользователями приложения, часть данных - поставляются извне для анализа, в приложение легко добавлять новый отчет или новую редактируемую сущность.

Для того, чтобы добавить новый отчет, достаточно написать прототип его на SQL - диалекте вашей СУБД, добавить 1 функцию - запрос типа select и 1 Result map в настроечный файл MyBatis и связать с одной функцией в DAO, которая возвращает множество строк отчета. Также надо создать класс - строка соответствующего отчета. 

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

А если нужно добавить настройки, дополнительный фильтр, сортировку, группировку и вообще что - то меняет не слишком сильно наш SQL - запрос, мы можем собирать наш запрос в зависимости от флагов, которые мы передадим в вызов функций MyBatis.

И все, никаких копипаст класcа с названием YetAnotherReportAboutBlaBla.

Если же что - то изменится в схемах бинов, мы можем дешево и сердито делать ALTER TABLE или править бины автоматически, добавляя нужные аннотации или nap - файлы.

Если вы грамотно напишите ваш DAO, то в случае полного отказа от Hibernate вы почти не заметите этого. Точнее все остальные слои вашего приложения. Вы - то заметите, вам переписывать.


MyBatis и его область

Если таблицы вашей БД удовлетворяют НФ любой степени тяжести и вам хочется оставить за фасадом детали преобразования данных БД в объекты, то MyBatis - лучший путь. У него пока плохо с автогенерацией ResultMaps по таблицам и с настройками оптимального взаимодействия с БД.

Но любой сложный отчет, узкое место, где нужен чистый SQL будут хорошо прикрыты MyBatis. Все что было в вашем приложении в виде JDBC - лапши должно быть внутри этой ORM. Это те участки, в ходе работы с которыми стало ясно, что Hibernate споткнулась.

Впрочем, не за горами генератор CRUD - запросов для MyBatis, аналог Reverse Engeneering.

Hibernate и его область

Бины, оформленные по стандарту JPA, такие вкусные и удобные для наших приложений. Удобные настройки работы с БД, быстрота внесения изменений в схему или в классы приложения. Все это поле игры Hibenate. 

Простые коллекции, удобные зависимости, CRUD - операции, большое коммьюнити, множество книг - все это делает Hibernate лучшим средством для разработки прототипа вашего приложения.

Однако имеются проблемы с большими выборками, не всегда удобно работющим кэшем запросов и отдельными сложными случаями. Также вам придется работать в сложных условиях с производительностью, т.к. вы становитесть очнь далеки от плана запроса и оптимизатора запроса.

Проблемы при внедрении связки

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

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

Лучший способ - шаблоны для типичных фич и диаграммы dev - процессов (по аналогии с бизнес-процессами) по их добавлению.

У вас проект и что же вам выбрать?

Если ваш проект похож на app case #1, вы не любите писать SQL - запросы, реляционная алгебра для вас темный лес и ваша схема данных меняется как перчатки по зову аналитика, да и проект не слишком высоконагруженный - пользуйте Hibernate и не парьтесь. 

Причем, как прямой, так и обратной схемой. Прямая подойдет на этапе до выкладывания кода в production. Данные для вас на этом этапе не ценны, со скриптами, создающими базы возиться не хочется и вообще в гробу вы видали эту схему БД.

Однако, если вы не планируете забираться в дебри романтики настроек Hibernate, хотите воплотить нормальный MVC - паттерн, проект высоконагруженный, аналитика требуется сложная и нетривиальная - вам, как минимум, стоит перейти на MyBatis по части отчетов и извлечения полезной информации.

По итогу вы получите некий SQL Report layer в своем приложении, который будет легко поддерживать и править.    

А вот здесь мнения на англицком, и тут тоже.

Комментарии

  1. What is it? Russian paper about Hibernate...

    ОтветитьУдалить
  2. Алексей, а в чем преимущество MyBatis перед Native Sql http://docs.jboss.org/hibernate/orm/4.1/manual/en-US/html_single/#querysql ?

    Есть возможность делать маппинг в xml также:



    SELECT person.NAME AS {person.name},
    person.AGE AS {person.age},
    person.SEX AS {person.sex}
    FROM PERSON person
    WHERE person.NAME LIKE :namePattern


    Почему вы не стали использовать именно эту возможность, а добавили еще один фрэймворк MyBatis?

    Спасибо за ваш доклад http://www.youtube.com/watch?v=w_K9g6aYJLI

    ОтветитьУдалить

Отправить комментарий

Популярные сообщения из этого блога

Cassandra, мой первый кластер и первая NoSQL

10 причин раздражаться при использовании Apache Spark

Big Data on your local machine : Installing Hadoop Cluster (Multi Node regime)

Virtual Box - много маленьких машинок внутри одной.