Поднятие 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.

Комментарии (16)


  1. tonyvelichko
    13.05.2015 19:59

    Я сталкивался с подобными трудностями при интеграции JPA + JTA в OSGI окружение. Решение вышло немного другим.

    • DataSource -> DataSourceFactory (org.osgi.service.jdbc.DataSourceFactory)
    • Внутри DataSourceFactory проверяется есть ли доступный TransactionManager (мы используем geromnio tm) и возвращается декорированный DS.
    • Далее вешается листенер на старт бандлов (org.osgi.framework.BundleListener) и проверяется наличиее мета тега Meta-Persistence
    • При нахождении этого тега берется persistence.xml и начинается инициализации EMF
      1. Формируется смешанный класслоадер для конкретной реализации JPA (eclipselink или hibernate) и бандла с ресурсом persistence.xml
      2. Формируется DataSource из настроек в persistence.xml и переопределений в системных настройках.
      3. Выбирается стратегия использования транзакций
      4. Вместо конкретной инициализации EMF подсовываем прокси EMF который в свою очередь имплементирует org.osgi.framework.ServiceFactory
         4.1 Далее на первый вызов getService(Bundle bundle, ServiceRegistration registration) инстациируется сам EMF и увеличивается счетчик ссылок на сервис
         4.2 На вызов метода ungetService(Bundle bundle, ServiceRegistration registration, Object service) происходит провека количества ссылок на сервис и если оно равно нулю то EMF уничтожается.


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


    1. Alexins Автор
      13.05.2015 21:35

      В OSGi, для JTA сейчас используется описанный вами подход.
      В файле манифеста указывается параметр «Meta-Persistence». Всю работу взваливает на себя система хуков (Bundle Hook Service
      Specification).

      JTA не всегда подходит под специфику задач. Скажем, есть у нас несколько версий документов. Для понимания, представим документ в виде XML структуры (xsd описание). По мере развития системы структуры документов изменялись. В системе есть WEB сервис для импорта документов. Для примера, скажем, документов из системы 1С.
      Напрашивается решение из нескольких OSGi компонентов, каждый для своей версии структуры документов.
      Каждый OSGi компонент приводит к сканированию набора классов, Entity компонентов.
      Через некоторое время, старые версии документов будут встречаться всё реже и реже. Значит, наша система должна уметь каким-то образом выгружать все классы OSGi компонентов. В том числе, весь Java Reflection, задействованный в MyBatis или в JPA.
      На текущий момент, единственным решением является «Declarative Services Specification». Сведения о сервисе опубликованы в реестре, а классы не подняты. При освобождении сервиса, ungetService, класс может быть выгружен OSGi Framework. Далее, GC может очистить раздел JVM permgen от занимаемых классов.

      OSGi hook не дадут нам нужного эффекта — освобождения JVM permgen. Нужно полностью останавливать компонент.

      JTA реализация в JPA выглядит красиво для конечного потребителя. На самом деле, это очень большой и запутанный раздел. EntityManager не может ответить, работает ли в соединение в распределенной транзакции или нет. Даже запросив TransactionManager на предмет активности транзакции, мы не знам, работает ли текущий EntityManager в рамках этой транзакции или в рамках другой транзакции.

      TransactionManager tm;
      Transaction t_old = tm.getTransaction();
      tm.suspend();
      Transaction t_new = tm.begin();
      

      Вот, где окажется flush или persist?
      Я скажу — в t_old. А ведь мы сказали, затормозить старую транзакцию и начать новую.

      В будущей версии mybatis-guice этот вопрос решен. Весь код, в методе выполнится в новой транзакции.
      @Inject MyMapper mapper;
      
      @Transactional(Transactional.TxType.REQUIRES_NEW)
      void doFoo() {
      	mapper.doAny();
      }
      

      А в JTA, нам надо открыть новый EntityManager. Поправьте меня, если это не так.


      1. tonyvelichko
        14.05.2015 00:45

        Все почти так. EM нужно создавать в контексте открытой транзакции, в этом случае к EM она будет привязана. Даже проще говоря нужно открыть Connection и он будет к привязан к тразакции, а уже к Connection будет привязан EntityManager. (и может быть как XA так и простой)

        Как в вашем примере, будет создано ровно тоже самое, будет открыто новое соединение, и оно будет привязано к транзакции.

        Простейший пример:
        Есть JTADataSource (например org.apache.commons.dbcp2.managed.BasicManagedDataSource)
        Есть TM

        TransactionManager tm;
        DataSource ds;
            
        Transaction t1 = tm.begin();
        Connection conn = ds.openConnection();
        
        ....
        conn.close();
        t1.suspend();
        Transaction t2 = tm.begin();
        conn = ds.openConnection();
        
        ....
        conn.close();
        t1.commit();
        t2.commit();
        


        1. Alexins Автор
          14.05.2015 14:52

          Другими словами. Когда создается новый объект EntityManager всё решается. Все зарегистрированные и измененные объекты, сохраняются этим менеджером.
          А вот с соединениями к базе данных беда. Да мы можем открыть новое соединение в новой транзакции. Но все изменения объектов, сделанные до открытия транзакции, выполняться в новой транзакции.
          Значит, нам нужно открепить объект от старого EM (detach) и прикрепить к новому EM (push). После завершения вложенной транзакции, вернуть объект в старый EM. Эта процедура для того, чтобы лишний раз не поднимать часть объектов из базы данных. Ведь в разных EM кеш объектов не пересекается.
          Немного сложная технология. А если есть прикрепленные объекты (one2one, one2many, many2one), тогда, после открепления основного объекта, надо откреплять все сопроводительные объекты.
          Мне это немного надоело в JDO. По этой причине перешел на MyBatis.

          JPA и JDO, красивые технологии, но в них столько нюансов, что иногда хочется выключить монитор, чтобы не видеть стек невероятных ошибок.


      1. tonyvelichko
        14.05.2015 01:08

        А вот выгрузка класса произойдет только тогда когда не будет на него ссылок, а ссылки остануться как минимум в бандле после первой загрузки, потому что каждый бандл внутрни себя содержит свой класслоадер, и если посмотреть на реализацию например в felix (org.apache.felix.framework.BundleWiringImpl.BundleClassLoader) он наследуется от java.lang.ClassLoader в котором загруженные классы попадают в кеш, соотвествеено они соберуться только после того как пропадут ссылки на класслоадер, а на него пропадут ссылки после деинсталяции бандла (судя по коду).


        1. Alexins Автор
          14.05.2015 14:28

          Хорошо, каждый компонент, OSGi bundle, загружается в своем ClassLoader. И выгружается, как только пропадают связи других компонентов на текущий компонент. Это теория. А что у нас с практикой?
          На практике совсем плохо. При поднятии компонента, BundleListener читает манифест и видит параметр «Meta-Persistence». Создается OSGi Service, который привязан к данному компоненту. Далее, этот сервис регистрируется в ServiceTracker. Для чего понадобился сервис? Для итого, чтобы он мог свободно получать доступ к классам Entity объектов. Сервис создает JTA фабрику.
          Как вы думаете, когда освобождается данный сервис? Правильный ответ — когда OSGi Framework останавливает OSGi Bundle, в котором зарегистрирован сервис. Другими словами, по доброй воле сервис не останавливается. Получается, что память от созданных объектов освобождается, а сами классы не выгружаются, так как JTA фабрика не разрушается в работающем сервисе. Как то так.

          Альтернатива этому мероприятию — Declarative Services. OSGi Framework поднимает все компоненты (OSGi bundle). Если есть активатор, он будет запущен. Есть параметр «Meta-Persistence», система создаст сервис с поднятой JPA фабрикой. Есть spring или blueprint, создаст сервисы для этих служб.
          Декларативный сервис — может тихо проследовать дальше, не поднимая классов. Да, есть аналог BundleActivator, который заставит загрузить классы.
          Декларативный сервис тем и полезен, что создается по требованию, когда из ServiceReferenсe требуем сам сервис. Как только сервис освобождается, с помощью ungetService, он сразу разрушается. Ни кто не держит BundleContext и классы из ClassLoader могут свободно выгружаться.
          Если созданный декларативный сервис захватить с помощью ServiceTracker, мы не получим выигрыша. ServiceTracker по доброй воле не отпускает захваченные сервисы. Ему надо говорить close().

          Честно, я не видел спецификации, как в JPA можно динамически загружать и выгружать сведения об Entity компонентах. В JDO спецификации такой функционал предусмотрен.
          Зачем это нужно? Для примера рассмотрим WEB сервис, принимающий разные типы документов в систему. Каждый вызов метода приема нового документа ведет к обработке одного типа документа. Десятки других типов документов не нужны. Процесс поднятия JTA Factory очень дорогая операция. По этой причине, она идет в момент поднятия OSGi Bundle и регистрации сервиса.
          В MyBatis можно загружать mapper интерфейсы по необходимости. Тем самым, поднятие фабрики в MyBatis становится дешевой операцией.


          1. tonyvelichko
            14.05.2015 16:26

            только создается ServiceFactory а не сам сервис, я писал выше. Как только никто не использует этот сервис, в ServiceFactory разрушается EMF. В этом то и дело что SCR (Declarative Services) как раз и работают имено через SF.

            Декларативный сервис тем и полезен, что создается по требованию, когда из ServiceReferenсe требуем сам сервис. Как только сервис освобождается, с помощью ungetService, он сразу разрушается.


            Это не совсем верно, точнее это верно только для DelayedComponent, и то в зависимости от настроек. Для остальных например в SCR примерно следующий код:
                public void ungetService(Bundle bundle, ServiceRegistration registration, Object service) {
                    if(usesCount.get() > 0){
                        long usesCountNow = usesCount.decrementAndGet();
                        log.debug("== Uses count of {} service are {}", original.getClass(), usesCountNow);
                    }
                }
            


            Реализуется это все через ServiceFactory.

            /**
             * Allows services to provide customized service objects in the OSGi
             * environment.
             * 
             * <p>
             * When registering a service, a {@code ServiceFactory} object can be used
             * instead of a service object, so that the bundle developer can gain control of
             * the specific service object granted to a bundle that is using the service.
             * 
             * <p>
             * When this happens, the {@code BundleContext.getService(ServiceReference)}
             * method calls the {@code ServiceFactory.getService} method to create a service
             * object specifically for the requesting bundle. The service object returned by
             * the {@code ServiceFactory} is cached by the Framework until the bundle
             * releases its use of the service.
             * 
             * <p>
             * When the bundle's use count for the service is decremented to zero (including
             * the bundle stopping or the service being unregistered), the
             * {@code ServiceFactory.ungetService} method is called.
             * 
             * <p>
             * {@code ServiceFactory} objects are only used by the Framework and are not
             * made available to other bundles in the OSGi environment. The Framework may
             * concurrently call a {@code ServiceFactory}.
             * 
             * @param <S> Type of Service
             * @see BundleContext#getService(ServiceReference)
             * @ThreadSafe
             * @version $Id: 535776e702ec5ace54f577218ff8f7920741558b $
             */
            
            public interface ServiceFactory<S> {
            


            На выгрузке классов никто не экономит, вот посмотрел на работаюший пару месяцев сервис там как загрузились 2,5к классов так и остались загруженными, если выгрузить бандл классы этого бандла будут выгружены, в противном случае они останутся загруженными.


            1. Alexins Автор
              14.05.2015 23:14

              Динамические сервисы, по умолчанию, являются уничтожаемыми SCR компонентами (DelayedComponent).
              Если SCR компонент не реализует интерфейсы, будет создан сразу (Immediate).
              Если для SCR компонента указать название фабрики, будет режим фабрики.

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

              Теперь представим работу с уничтожаемым динамическим сервисом. При первом запросе сервиса, Framework поднимет реализацию сервиса. Каждый Bundle, запрашивающий данный сервис, получит один и тот же объект. При разрегистрации компонента, когда счетчик использования достигает нуля, объект сервиса будет уничтожен.

              Прочувствуйте разницу на сложном примере. У нас есть JAXB объекты, генерированные из xsd схемы. Для работы нам надо поднять JaxbContext. Если количество классов в схеме около сотни, процесс будет достаточно медленным. Можно поднять JaxbContext в фабрике и передавать его в каждый создаваемый экземпляр. Когда все экземпляры сервисов будут уничтожены, фабрика может обнулить данный параметр. А можно поднять уничтожаемый динамический сервис. При активации, он поднимет JaxbContext. При деактивации, JaxbContext будет уничтожен по ходу уничтожения объекта сервиса.

              Теперь подумаем, как ведет себя JPA в фабрике. В пределах одной транзакции сервис запрашивается из двух компонентов, OSGi bundle. Для каждого будет создан свой сервис со своим EntityManager. Первый bundle создал часть объектов и не сделал flush. После этого работает второй компонент. Программист думает, что часть нужной информации доступна, так как она сделана в этой транзакции. Но в базе данных объектов еще нет и сервер базы данных отдает устаревшие данные. Теперь, раздается общий commit по транзакции. Как вы думаете, какой из двух EntityManager запишет свои данные раньше? Ответ — второй. Для этого надо углубиться в диаграммы описания работы JTA транзакций.
              Вы разве этого хотели достичь своей фабрикой?
              Я точно, так не хочу.


  1. aparamonov
    15.05.2015 10:00

    Ребята, а приведите пожалуйста примеры модульных систем (где в каждом модуле свои сущности), где есть требование выгружать динамически модули/загружать новые? И откуда растут такие требования?


    1. Alexins Автор
      15.05.2015 10:43

      В качестве примера можно рассмотреть обработку данных с федерального сайта государственных закупок.
      За прошлый год структура документов сменилась семь раз. В каждой версии порядка 40 типов документов. 7 * 40 = 280 типов документов. Получается, что в системе, для каждой версии свой компонент с JAXB объектами. Средний объем бинарных кодов JAXB классов от 4М до 6М. При таких объемах начинаешь воевать за крошки в JVM permgen. Далее, строим некую систему хранения данных своей организации. Появляется новая версия документов. Зачем ломать тот код, что уже работает? Делаем новую систему раскладки под новую версию документов.
      Задачи использования полученных данных можно придумать самому. И это будет уже другая история.

      Проблема не столько в Entity объектах. В MyBatis можно обойтись объектами, созданными из JAXB модели. Главная задача, это выгрузка классов. А для этого надо аккуратно убирать все классы обработки, где используются импорты JAXB классов. Кто у нас главный поставщик этих классов обработки? Правильно, сервис со своей JPA фабрикой или MyBatis фабрикой.


      1. aparamonov
        15.05.2015 11:18

        А зачем решать задачу выгрузки классов в Вашем примере? Чтобы 7 раз в год обновить версии документов?


        1. Alexins Автор
          15.05.2015 16:02

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

          Можно придумать другую задачу, где регулярно меняются форматы документов. В бухгалтерском деле это случается довольно часто. Документы должны храниться от 3 до 5 лет.

          Специфика системы такая, что может прийти документ старого и нового форматов.

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


  1. yetanothercoder
    18.05.2015 11:20

    osgi по «историческим» или иным каким то объективным причинам?


    1. Alexins Автор
      18.05.2015 16:28
      +1

      Как бы сказать получше.
      1) У меня проект с использует Apache Camel. В OSGi все Camel процессы стартуют автоматически.
      2) В Apache Karaf есть хорошая WebConsole для управления параметрами сервисов. Изменил, нажал сохранить, система работает с этими параметрами. Опять же, изобретать и отлаживать ничего не надо.
      3) Большое количество версий документов. На уровне OSGi сервисов я указываю параметр версии обрабатываемых документов. Указываю в фильтре версию обрабатываемого документа. Система возвращает нужный сервис. В EJB я такого функционала не обнаружил.
      4) В сочетании Apache Camel и OSGi сервисов, я использую динамическую регистрацию процессов (recepientList). Тем самым, получаю одну систему приема документов и много систем обработки.
      5) Можно поставить hawt.io для визуального отслеживания производительности системы.

      Да бы не писать разные наборы Entity компонентов под разные системы обработки, использую трансформацию XML => JAXB => MyBatis. Да, некоторые XML радуют размерами в десятки мегабайт.
      Использую отдельный OSGi компонент (bundle + service) под обработку документов одной версии.
      Система поднимает OSGi сервис для обработки документа определенной версии. После обработки, сервис становится не востребованным и Framework может освободить память.

      Делаем упор на то, что версий документов может быть много. Поднимать Entity, сразу для всех типов документов одной версии, дело не благодарное. В MyBatis используется динамическое поднятие интерфейсов обращения к базе данных. Это значительно ускоряет процесс. В рамках JPA надо дробить систему еще сильнее — один OSGi bundle для каждого документа одной версии.

      Везде я указывал, что можно использовать WEB сервис для получения входного документа. На самом деле, в проекте документы появляются по расписанию и большими пачками. До тысячи документов в пачке. Apache Camel процессы позволяют вести обработку в несколько потоков. Логично предположить, что маленькие документы обрабатываются быстрее, а большие медленнее.


      1. yetanothercoder
        18.05.2015 17:02

        караф какой версии?
        был опыт карафа 2 — так там даже перегрузить бандл было нельзя — либо ничего не менялось либо глючило либо висло, тоже самое при попытке запустить один и тот же бандл с разными версиями, т.е. даже основная фишка osgi «hot redeploy» не работала и приходилось рестартовать караф всегда. Ну и основное неудобство — использование только osgi версий библиотек, которых либо может не быть вообще и тогда пляска с бубном и упаковка вручную либо есть но почти всегда отстают от обычной, классический пример — guava: osgi версия древняя.


        1. Alexins Автор
          19.05.2015 09:13

          Проект Apache Karaf развивается не так быстро. Примерно один фикс релиз в пол года. Задержки вызваны медленным развитием дополнительных компонентов. Для примера, 4 версия всё еще на стадии тестирования, хотя первые альфы были года 2 назад.
          Так что, все работают на версиях 2 или 3. Должен отметить, что есть очень интересная версия — 2.4.x. По сути, это стабильный функционал от 4 версии. Если сравнивать версии 2.4.x и 3.0.x, то младшая версия выглядит гораздо привлекательнее.
          Правда, в выборе версии Apache Karaf не последнюю роль играет используемая версия Apache Camel.
          Apache Camel развивается гораздо быстрее. Жизненный цикл одного фикс релиза составляет 3 месяца. На сайте проекта есть таблица соответствия версий проектов Apache Camel и Apache Karaf. В выборе версий желательно придерживаться данных этой таблицы. В последнее время, весной, проект Apache Camel начал хандрить. Люди постарались выпустить версию 2.15.0. В этой версии очень много интересных решений и многие ожидали. В частности, этот релиз переходил на Apache Karaf 2.4.x. Как обычно, люди старались и некоторые ошибки просто упустили.
          Так к слову, Apache Camel потерял функционал управления из командной консоли Apache Karaf. Я отметил данный нюанс на форуме проекта. Разработчики постарались в сжатые сроки, через 20 дней, выдать исправленную версию. Как говориться, спешка нужна при ловле блох. Пришлось еще через месяц выпускать новую версию фикс релиза.

          У вас проблема роста системы — компонент нельзя перегрузить.
          Если компонент используется как библиотека классов, выгрузить ее почти не реально. Надо ждать, когда остановятся другие компоненты.
          Если компонент предоставляет сервис, надо смотреть, где находится интерфейс. Если интерфейс лежит в том же компоненте, та же история, как в первом случае.
          Рецепт один. Предоставляйте не реализацию классов, а интерфейсы. Интерфейсы держите в отдельном компоненте.

          Горячая замена. Да, есть проблема в OSGi Blueprint. Не хочет отпускать захваченный сервис. По этой причине надо останавливать компонент (bundle) сервиса и запускать снова.

          По поводу использования не OSGi библиотек. По началу, я тоже ломал голову, что надо делать OSGi wrapper. Решение пришло с другой стороны. В Apache Karaf есть файл описания плана развертки особенностей (features). Пример команды установки
          features:instal war

          Пример установки Google Guava, библиотеки, которая давно обзавелась OSGi манифестом:

            <bundle>mvn:com.google.guava/guava/15.0</bundle>
          


          Пример установки JDBC драйвера, не обладающего OSGi манифестом:

            <bundle>wrap:mvn:com.oracle/ojdbc14/10.2.0.5</bundle>