[Из песочницы] MyBatis и OSGi
Поднятие MyBatis Мало кто предполагает, какие трудности постигают нас на пути внедрения уже знакомых технологий в новые системы. Одна из не очевидных трудностей — это подружить MyBatis с OSGi компонентами. Самая неординарная трудность — это спрятать свои классы в приватную часть системы. Мы ведь не хотим выставлять свои объекты наружу. Как в корпусе телефона прячем свою SIM карту и MicroSD карту. Да, мы знаем, что эти штуки есть, но ни кому показывать не хотим. То же самое с объектами внутри OSGi компонента (bundle).Так вот, MyBatis, будучи сторонней библиотекой, не может достучаться до приватных объектов. А нам так хочется закрыть от всех свои тайны. Аж руки чешутся и стул скрипит от нетерпения.Для нормальной работы нам надо научиться готовить MyBatis частями. Начнем с того, что конфигурацию лучше всего настраивать в Java коде, а не в привычном XML. Поскольку у нас OSGi, то и доступ к базе данных идет на уровне OSGi сервиса, реализующего интерфейс DataSource.
Никто не скажет вам, как настроить MyBatis для работы с распределенными транзакциями. Их просто нет на уровне MyBatis Framework. Приходится пользоваться сторонними системами. Одно мы знаем точно, что в этом случае надо указать менеджер транзакций — ManagedTransactionFactory. Для локальных транзакций надо выбрать JdbcTransactionFactory.
Дальше начинается самое интересное — это классы, накладывающиеся на параметры и результаты запросов. Эти классы мы как раз прячем в приватной части OSGi компонентов. Тут работает только одно решение — назначить каждому классу псевдоним. В этом случае MyBatis создаст карту (map) классов. В качестве ключей будет псевдоним класса, а значением будет класс. Объект конфигурации, будучи созданным в локальном загрузчике, имеет нормальный доступ к классу из этой карты (map). Динамическое создание объекта нужного класса не вызывает проблем.
Если мы забудем присвоить псевдоним и в xml укажем полный путь поднимаемого класса, MyBatis не сможет поднять этот класс. Фабрика поднятия объектов создавалась на уровне компонента MyBatis (bundle) и не имеет доступа к загрузчику классов нашего OSGi компонента. MyBatis банально отругает нас ошибкой на стадии чтения результатов выполнения SQL запроса.
В MyBatis очень удобно использовать mapper интерфейсы. Здесь можно пойти двумя путями в хранении SQL запросов. Можно хранить запрос в одноименном XML файле, расположенном в том же пакете, где живет интерфейс. Можно разместить запрос в аннотации к вызываемому методу. В случае аннотаций прописывать псевдонимы к классам не обязательно. MyBatis, обрабатывая аннотации, сделает эту работу самостоятельно. О, да, можно устроить полную чехарду из XML файлов и аннотаций. В XML формате есть красивое описание resultMap, а в аннотациях ему соответствует полная каша в нескольких методах. Зато в аннотации можно указать название resultMap из XML описания. Так же в XML описании есть интересный функционал — импорт частей SQL запросов. Это очень красиво выглядит, когда методы получают одинаковые объекты и отличаются только параметрами запросов. В аннотациях такой красоты не повторить.
Да, документация по MyBatis рекомендует регистрировать все XML файлы запросов в конфигурации. Я бы сказал, что надо регистрировать не XML, а интерфейсы (mapper). Система сообразит взять одноименный XML файл, рядом с интерфейсом. Но, как показывает практика, эту работу можно отложить до лучших времен. Скажем так, есть шанс, что некоторые запросы будут выполняться достаточно редко, что процедура поднятия этих запросов будет простои лишней тратой времени и памяти.
Производительность Тут мы плавно переходим к производительности. Производительность не в плане увеличения производительности сервера базы данных. Производительность в плане затрат на хранение и обслуживания объектов и запросов.Чем проще передаваемые в параметрах объекты и формируемые объекты в запросах, тем быстрее система их поднимает. Как было сказано, процесс поднятия можно отложить до лучших времен. Тем самым, сократив время первичной настройки MyBatis. Причем система не тратит особых усилий на разбор структуры классов в параметрах и результатах запросов. Достаточно один раз зарегистрировать класс и система будет использовать уже готовую карту разложения класса. За это отвечает класс org.apache.ibatis.reflection.Reflector. Надеюсь, в скором времени мы увидим немного другую конструкцию — org.apache.ibatis.reflection.ReflectorFactory.
Для чего сделано новшество ReflectorFactory?
Как было сказано выше, MyBatis запоминает один раз структуру объекта. Хорошо, если объектов относительно мало. Если объектов достаточно много, ну, скажем, несколько десятков мегабайт, и они используются довольно редко. В этом случае производительность системы становится ее слабой стороной. Никто по доброй воле не чистит память от ненужных объектов. В результате JVM хранит структуры не используемых классов в специальном разделе Java 7 permgen. Для чистки этого малого кусочка памяти будет использоваться ReflectorFactory. На текущий момент потеря настройки MyBatis (Сonfig) приводит к освобождению таких ресурсов, как память для хранения SQL запросов, интерфейсов маппинга. MyBatis, забывая о конфигурации, забывает и о ReflectorFactory, тем самым разрушает структуру используемых классов.
В чем же преимущество MyBatis от реализаций JPA или JDO?
Главное преимущество — это возможность динамической привязки большого количества объектов к базе данных и возможности забывать эти объекты. Ну вот, честно. Мы знаем, что JPA сканирует все классы на предмет присутствия JPA Entity аннотаций. Это замечательно. А что дальше? Дальше ничего. Нет сведений о том, как JPA будет забывать об этих классах.
Автор в JDO DataNucleuse делал реализацию динамического подъема классов в OSGi. Был сделан намек на то, что система не выгружает объекты при остановке OSGi компонента. Прошли годы, а выгрузки зарегистрированных объектов так и не появилось. Думаю, схожие проблемы присутствуют и в других реализациях JPA.
Распределенные транзакции Вторая тема, затронутая в статье — это распределенные транзакции. MyBatis не имеет реализации поддержки распределенных транзакций.Относительно недавно в MyBatis появился очень интересный и полезный объект — SqlSessionManager. Он позволяет использовать в одном потоке единственную сессию к базе данных. Сессии в разных потоках не пересекаются. Процесс открытия и закрытия сессии легко переносится в обработчики аннотаций. Этим пользуются почти повсеместно, в библиотеках на основе Spring, CDI, Guice. Везде можно найти аннотации управления транзакциями. И даже можно увидеть поддержку распределенных транзакций.
Но есть одна интересная хитрость в распределенных транзакциях. Это возможность запуска новой транзакции в рамках уже работающей транзакции. Ее название «Required New». Что же происходит с MyBatis? Всё просто и печально. Работая в том же потоке, объект SqlSessionManager возвращает ранее открытое соединение. То есть открывая новую транзакцию мы на самом деле забираем старое соединение в чужой транзакции.
Аварийное завершение вложенной транзакции не приведет к откату действий, так как все изменения шли в рамках внешней транзакции. Принятие вложенной транзакции и откат внешней приведут к потере изменений, которые планировалось сделать во вложенной транзакции.
В проекте mybatis-guice предложено решение такой деликатной проблемы. Для объекта SqlSessionManager формируется специальный ресурс XAResource. Контроллер транзакций, TransactionManager, управляет зарегистрированными XA ресурсами. Приостановка XA транзакции приводит к переключению информации о работающей сессии в SqlSessionManager.
Почему это появилось в mybatis-guice?
Опять же, всему виной OSGi. В OSGi есть своя спецификация, OSGi Blueprint. Она очень похожа на Spring Framework. Предок был у них один. По ряду причин эти два Framework конфликтуют. Либо первый, либо второй. Нет сладу. CDI Weld — это фишка для веб-приложений. Так уж постарались разработчики Weld, что прописали это полезное решение исключительно для веба. Вот и приходится искать такое решение, не мешающее жить среди шумных и не очень дружных соседей. Guice — та система, которая нормально уживается в рамках нескольких конфигураций, не мешающих друг другу.Скажем так, в одном компоненте легко живут две конфигурации. Одна для работы с локальными Jdbc транзакциями и вторая, для работы с распределенными транзакциями JTA. Наборы классов одни и те же, нет лишних аннотаций и xml описаний. Даже конфигурация MyBatis происходит одним и тем же методом в классе.
Надеюсь, в скором времени данный функционал появится в новой версии mybatis-guice.