2012-08-31

Едем во Львов

SpringByExample.com.ua едет во Львов. 7-го сентября я выступлю на встрече  JUG Lviv, а 8-го me & eugene проведем воркшоп. Регистрация на воркшоп тут. Спасибо Lohika за сотрудничество.


2012-08-29

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

Не так давно в одном из проектов стояла задача регулярного опроса базы данных и обработки записей полученных оттуда по мере их возникновения. Эту задачу мы решали с помощью Spring Task Scheduling. Начнем с конфигурации контекста:

Spring предоставляет удобный namespace task, который содержит все необходимое для создания Executor/Scheduler бинов. В моей конфигурации максимальное кол-во потоков, проводящих опрос базы данных - пять. <task:annotation-driven> элемент позволяет использовать аннотировнные компоненты для запуска задач, а также указывает executor/scheduler которые будут использоваться по умолчанию для запуска аннотированных методов.


Как видно из примера выше, для создания задачи достаточно аннотировать необходимый метод аннотацией @Scheduled, которой необходимо указать один из трех параметров: 
1. fixedDelay - период между завершением предыдущего запуска и началом следущего запуска задачи в миллисекундах
2. fixedRate - период между каждым запуском задачи в миллисекундах (считается от старта предыдущей задачи)
3. cron - указывает cron подобное выражение, в котором можно более точно указать периоды запуска задачи

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

После старта контекста Spring автоматически настроит необходимые Executor бины и будет выполнять метод pollDatabase раз в секунду (ну или чуть реже, учитывая время на выполнение самого метода).н

Правда, во время эксплуатации этого кода, возникла одна неприятная проблема, которая в общем-то не влияла на работоспособность системы, но оставляла мусор в логах. Дело в том, что выключая контейнер (Tomcat в нашем случае), Spring закрывает контекст и разрушает все бины, находящиеся в нем. При этом выполняемые запланированные задачи иногда разрушались после того как уже были разрушены сервисные бины; задачи пытались использовать разрушенные сервисные бины, вызывая различные странные исключения.

Таким образом, чтобы навести порядок в логах и очистить совесть, решили исправить ситуацию. Для того, чтобы корректно разрушить нашу компоненту с задачами, нам необходимо вклиниться в процесс разрушения контекста и закрыть выполняемые задачи до того, как разрушатся остальные бины. Для решения этой проблемы Spring предоставляет специальный интерфейс ApplicationListener, позволяющий реагировать на различные сообщения, связанные с контекстом.

Мы включили в нашу компоненту две новых зависимости (databasePollExecutordatabasePollScheduler), которые дали нам доступ к executor/scheduler, обьявленных в XML контексте. Также мы добавили в нее реализацию интерфейса, которая уничтожает executor/scheduler и связанные с ними задачи. Метод onApplicationEvent будет вызван Spring-ом до начала разрушения остальных бинов и контекста в целом, а это именно то что нам нужно.

2012-08-07

FAQ: Использование @Transactional аннотации

В этой статье я бы хотел рассказать о проблемах, которые часто возникают при использовании транзакций в Spring, а в частности, вопросы связанные с использованием аннотации @Transactional.

Начнем с общих вопросов.
Стоит ли использовать эту аннотацию на интерфейсах или на конкретных классах?

Простой ответ - используйте их на конкретных классах. С точки зрения построения сервисов, управление транзакциями - это детали реализации. В очень редких случаях имеет смысл делать транзакции частью контракта, если есть какие-то явные требования к наследованию транзакционных методов (transaction propagation), обьявленых в интерфейсе.

Кроме того, работоспособность аннотаций на интерфейсе не всегда будет корректной. Если стратегия проксирования классов будет выбрана не верно (например, proxy-target-class='true'), то @Transactional на интерфейсе вообще будут игнорироваться. Детальнее об этом можно прочитать в документации Spring.

Будет ли работать @Transactional на приватных методах класса?

Для начала, следует понять каким образом аннотации на методах порождают транзакции. Spring использует AOP для пре/постобработки аннотированных методов. Таким образом все зависит от имплементации AOP и способа вызова метода.

Если используется конфигурация по умолчанию, то класс будет обернут в прокси, и в этом случае только внешние вызовы методов будут транзакционными. А поскольку вызов приватных методов является внутренним для каждого конкретного бина, то @Transactional на нем будет игнорироваться. Аналогичная ситуация будет происходить даже в том случае если метод публичный - вызов такого метода из этого же класса никогда не начнет транзакцию.

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

Почему использовать @Transactional лучше на сервисных классах?

В первую очередь DAO бины, предоставляют примитивные операции над определенным доменом, тем не мнее являются плохими кандидатами для аннотации @Transactional. Конечно, такие транзакции будут очень короткими, это несомненный плюс. Но при этом теряется возможность объеденять несколько мелких операций в один блок, и в случае если, одна из мелких операций даст сбой (rollback), то остальные операции останутся закоммичеными, так как, проходили в отдельных транзакциях. Именно для этого и нужны сервисные бины - обьеденять логически совместимые операции в монолитные блоки. А для того чтобы явно подчеркнуть, что методы DAO бина должны выполнятся в транзакциях обьявленных во внешних сервисах, можно на DAO бин поставить аннотацию @Transactional(propagation = MANDATORY). Такие вспомогательные аннотации помогут быстрее находить ошибки, связанные с пропущенными аннотациями на сервисном уровне.

Стоит ли делать контроллеры транзакционными?

Если ваше приложение маленькое и сервисный слой вырождается в однострочные перевызовы DAO, то возможно, в этом случае и стоит ставить @Transactional на контроллерах. Ну а вобщем случае и в продолжение предыдущего вопроса хотел бы сказать, что мешать MVC и сервисный слой не очень красиво. Кроме того транзакционные контроллеры потребуют дополнительной конфигурации DispatcherServlet контекста и включения в него менеджера транзакций.

Как @Transactional работает в JUnit тестах?

Если Вы используете интеграционные тесты, то @Transactional аннотация обязательно Вам пригодится.

@Transactional аннотацию можно использовать как на уровне класса, так и на отдельных тест методах. В первом случае для каждого тест метода будет создаваться отдельная транзакция, если необходимо отключить использование транзакций для определенных тест методов, можно их пометить аннотацией @NonTransactional.

По умолчанию любой транзакионный тест всегда откатывается после завершения. Это поведение можно поменять либо с помощью вспомогательной аннотации @Rollback(false) применяемой на тесте, либо с помощью аннотации @TransactionConfiguration(defaultRollback=false), которая применяется на JUnit классе.

Следует отметить, что методы аннотированные JUnit аннотациями @Before/@After будут также запускаться в транзакции. Потому, если есть необходимость сделать какие-то подготовительные работы вне транзакции, их следует вынести в отдельные методы и пометить специальными аннотациями @BeforeTransaction/@AfterTransaction. Правда, не забудьте, что такие методы не будут запускаться вообще для нетранзакционных тест методов.

Почему некоторые обьекты остаются в базе после выполнения @Transactional Unit теста?

Самая частая причина - это использование @Transactional(propagation = Propagation.REQUIRES_NEW) в тестируемых классах. Эта аннотация указывает менеджеру транзакций открыть новую транзакцию при входе в метод, вне зависимости от того, существует уже открытая внешняя транзакция или нет. Поэтому все действия выполненные в таком методе не будут откатаны после завершеня тест метода.

2012-08-06

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

Привет, всем кто нас читает! Мы продолжаем печатать гостевые статьи. Сегодня у нас опять статья Олега Артемова - нашего давнего коллеги и эксперта по Spring. 

Добрый день всем ! Данная статья посвящена концепции ConversionService, которая была успешно применена в моем текущем проекте. Итак, преамбула:

Как мы видим, это обычная команда, которых в любом Spring – проекте миллионы. С помощью Spring ConversionService мы можем избавиться от примитивных типов в команде и использовать доменную модель. Итак, как это делается:


В документации описан способ немного проще. Моей целью являлось, чтобы конвертеры сами регистрировались в контексте. Итак, рассмотрим подробно:

  • ConvertersHolder – содержит в себе все конвертеры(GenericConverter, Converter, etc…). ExtendedConversionServiceImpl – более расширенная версия ConversionService, которая умеет конвертировать коллекции из одной в другую. 
  • HibernateDaoGenericConverter – generic конвертер. Одним этим конвертером мы можем конвертировать строки в объекты доменной модели. 
  • StringToEntityConverter – интерфейс, с помощью которого строка конвертируется в доменный объект 
  • AutoDetectFormattingServiceFactoryBean – бин, который регистрирует конвертеры. 

Я привел имплементацию метода convert, которая сделана в базовом классе DAO. На самом деле она может быть абсолютно любая. Теперь наша команда выглядит иначе(поля все равно должны совпадать с названиями параметров из реквеста).

Как и прежде, её можно валидировать (см. предыдущие статьи) и решать, что же будет в случае ошибок. Из минусов:

  • Данная концепция может быть применена и для конвертации параметров метода контроллера, но стандартные аннотации валидации на них не действуют, поэтому приходится использовать везде команды.
Подведем итоги. С помощью ConversionService можно избавиться от постоянных вызовов DAO для конвертации параметров в доменную модель и сделать наш код более красивым.

Оригинал статьи тут.  Документация тут.