SpringByExample.com.ua едет во Львов. 7-го сентября я выступлю на встрече JUG Lviv, а 8-го me & eugene проведем воркшоп. Регистрация на воркшоп тут. Спасибо Lohika за сотрудничество.
2012-08-31
2012-08-29
Использование Spring task scheduler
Не так давно в одном из проектов стояла задача регулярного опроса базы данных и обработки записей полученных оттуда по мере их возникновения. Эту задачу мы решали с помощью Spring Task Scheduling. Начнем с конфигурации контекста:
Spring предоставляет удобный namespace task, который содержит все необходимое для создания Executor/Scheduler бинов. В моей конфигурации максимальное кол-во потоков, проводящих опрос базы данных - пять. <task:annotation-driven> элемент позволяет использовать аннотировнные компоненты для запуска задач, а также указывает executor/scheduler которые будут использоваться по умолчанию для запуска аннотированных методов.
Таким образом, чтобы навести порядок в логах и очистить совесть, решили исправить ситуацию. Для того, чтобы корректно разрушить нашу компоненту с задачами, нам необходимо вклиниться в процесс разрушения контекста и закрыть выполняемые задачи до того, как разрушатся остальные бины. Для решения этой проблемы Spring предоставляет специальный интерфейс ApplicationListener, позволяющий реагировать на различные сообщения, связанные с контекстом.
Мы включили в нашу компоненту две новых зависимости (databasePollExecutor, databasePollScheduler), которые дали нам доступ к executor/scheduler, обьявленных в XML контексте. Также мы добавили в нее реализацию интерфейса, которая уничтожает executor/scheduler и связанные с ними задачи. Метод onApplicationEvent будет вызван Spring-ом до начала разрушения остальных бинов и контекста в целом, а это именно то что нам нужно.
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 закрывает контекст и разрушает все бины, находящиеся в нем. При этом выполняемые запланированные задачи иногда разрушались после того как уже были разрушены сервисные бины; задачи пытались использовать разрушенные сервисные бины, вызывая различные странные исключения.
Правда, во время эксплуатации этого кода, возникла одна неприятная проблема, которая в общем-то не влияла на работоспособность системы, но оставляла мусор в логах. Дело в том, что выключая контейнер (Tomcat в нашем случае), Spring закрывает контекст и разрушает все бины, находящиеся в нем. При этом выполняемые запланированные задачи иногда разрушались после того как уже были разрушены сервисные бины; задачи пытались использовать разрушенные сервисные бины, вызывая различные странные исключения.
Таким образом, чтобы навести порядок в логах и очистить совесть, решили исправить ситуацию. Для того, чтобы корректно разрушить нашу компоненту с задачами, нам необходимо вклиниться в процесс разрушения контекста и закрыть выполняемые задачи до того, как разрушатся остальные бины. Для решения этой проблемы Spring предоставляет специальный интерфейс ApplicationListener, позволяющий реагировать на различные сообщения, связанные с контекстом.
Мы включили в нашу компоненту две новых зависимости (databasePollExecutor, databasePollScheduler), которые дали нам доступ к 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) в тестируемых классах. Эта аннотация указывает менеджеру транзакций открыть новую транзакцию при входе в метод, вне зависимости от того, существует уже открытая внешняя транзакция или нет. Поэтому все действия выполненные в таком методе не будут откатаны после завершеня тест метода.
Начнем с общих вопросов.
Стоит ли использовать эту аннотацию на интерфейсах или на конкретных классах?
Простой ответ - используйте их на конкретных классах. С точки зрения построения сервисов, управление транзакциями - это детали реализации. В очень редких случаях имеет смысл делать транзакции частью контракта, если есть какие-то явные требования к наследованию транзакционных методов (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. На самом деле она может быть абсолютно любая. Теперь наша команда выглядит иначе(поля все равно должны совпадать с названиями параметров из реквеста).
Как и прежде, её можно валидировать (см. предыдущие статьи) и решать, что же будет в случае ошибок. Из минусов:
- Данная концепция может быть применена и для конвертации параметров метода контроллера, но стандартные аннотации валидации на них не действуют, поэтому приходится использовать везде команды.
Subscribe to:
Posts (Atom)