Pull to refresh

Транзакции в Spring

Reading time5 min
Views38K
Статья описывает пример по работе с транзакциями в популярном framework Spring. Статья предполагает, что вы знакомы с java и spring. Рабочий пример кода можно скачать с sf



Есть две стратегии управления: программное управление (пишем код вручную) и декларативное (настраиваем границы транзакций в XML конфигурациях). В дополнение мы можем работать с разными типами транзакций: локальными и распределенными.

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


Коротко о транзакциях


Под транзакцией мы понимаем ряд действий (не обязательно в БД), которые воспринимаются системой, как единый пакет, т.е. или все действия проходят успешно, или все откатываются на исходные позиции.

Рассмотрим классический пример с переводом денег с одного счета на другой. Перевод денег это наш «пакет», который содержит два действия: уменьшает баланс счета А и увеличивает баланс счета Б. Очевидно, что мы не можем уменьшить первый счет, и провалиться в увеличении второго — в случае ошибки мы должны откатить обе операции.

Как мы говорили ранее есть два типа транзакций — локальные и распределенные. Локальная транзакция работает с одним источником, например одна БД; распределенная использует несколько — например jms source и БД.

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

Но есть путь значительно более простой — использовать любой jEE сервер, т.к. они содержат уже настроенные компоненты. В нашем примере мы попробуем использовать JBoss 5.

Для более подробной информации о механизмах транзакций рекомендую посмотреть wikipedia.

Окружение


Во первых нам понадобятся источники для данных: создадим две базы в mysql и назовем их Alfa и Beta. В альфе сделаем две таблицы: alfa1 и alfa2; структуры таблиц одинаковы и содержат поля id и name. Дополнительно добавим уникальный индекс на колонку name для alfa1. В базе Beta создадим только одну таблицу с теми же полями и уникальным индексом.

Обязательно надо указать engine для таблиц — InnoDB, иначе транзакции не будут работать.
В архиве с примером есть скрипт для создания всех структур.

Use case


Наш сценарий содержит две операции: каждая вставляет данные в одну таблицу. Так как таблица alfa2 не содержит индекса, то мы можем вставлять любое количество дубликатов. Дубликаты в остальных таблицах не возможны, и, как следствие, мы должны получать ошибку при повторной вставке и откат транзакции.

Каждый пример надо запускать дважды: первый старт завершится нормально и в обоих таблицах будут новые записи; повторный запуск завершится ошибкой.

Пример локальной транзакции


Локальная транзакция работает с одной базой: Alfa. Подключение к базе описывается в стандартном spring контексте, вместе с описаниями data access layer и сервис layer. Там же указываются конфигурации hibernate.
Data access object содержит два метода для вставки данных (alfa1 alfa2).
Сервис layer использует dao для непосредственной работы.

Для начала попробуем программный метод управления.
Создаем контекст (programContext.xml) и объявляем все бины: подключение к базе, конфигурация hibernate, dao и service. Все бины используют dependency injection.
Самый главный бин для нас — transaction manager. Мы объявляем стандартный класс и используем его в нашем сервисе. В Service указан пример использования — это единственный нюанс.
Так же делаем класс для запуска наших структур, который инициализирует контекст и вызывает наш сервис layer. Первый запуск выполняется нормально и мы видим данные в таблицах alfa1 и alfa2. Повторный запуск вызывает ошибку — так как есть уникальный индекс на alfa1, но откат произойдет и в таблице alfa2, что нам и было необходимо.
В этом подходе нам пришлось явно использовать менеджер транзакций, т.е. наш код стал зависим от внешних библиотек. Мы можем избежать этого используя декларативный подход к управлению транзакциями.

Мы создадим новый объект в service layer, в котором не будет никакого кода по управлению транзакциями, и объявим все бины в новом контексте
(declarativeContext.xml). Подключения к базе, настройки, transaction manager — все это аналогично преведущему примеру. Единственная разница в определении Service, теперь он не использует transaction manager.
Но сами транзакции нам нужны по прежнему, и мы должны информировать об этом spring. В общем, нам надо объявить два элемента: какие методы мы хотим поместить в транзакцию, и какой менеджер использовать.
Правило для определения метода: execution(* service.DeclarativeTx.*(..)) — т.е. мы выбираем все методы нашего service. А следующая настройка говорит, что мы будем использовать менеджер по умолчанию.
Сотрите данные из таблиц и запустите пример дважды. Как обычно, первый запуск выполняется нормально, а второй завершается ошибкой, вместе с откатом всей транзакции.

Пример распределенной транзакции


Большинству приложений достаточно локальных транзакций для успешной работы. Но есть ряд приложений, которые вынужденны работать с данными распределенными по многим источникам. В этом случе для управления изменениями используются распределенные транзакции.
В целом все работает аналогично обычным транзакциям, за исключением того, что нам надо синхронизировать работу всех источников. Тут помогает отдельная подсистема: менеджер управления распределенными транзакциями.
Менеджеры выпускаются большик количеством вендоров в качестве отдельных программных продуктов, т.е. мы должны скачать, установить и сконфигурировать систему. Для этого придется прочитать документацию и тп, но мы выберем путь проще: современные jee серверы приложений содержат встроенные, уже сконфигурированные менеджеры.
Мы будем использовать jboss 5, хотя можно использовать любой другой. Благодаря гибкости spring на не надо делать сервер-зависимых изменений.

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

Деплой приложения будет проходить в JBoss, который мы должны предварительно настроить. Как минимум нам нужен доступ в обе базы — создадим два файла для конфигурирования datasources и скопируем их в server/default/deploy. Сами файлы доступны в архиве с примером. Возможно вам надо поменять имя пользователя и пароль, и, самое главное, не забыть положить в lib сервера jdbc драйвер для myysql.

Само приложение так же лежит в архиве. Рассмотрим его подробнее, вместо написание аналога с нуля.
Проект содержит jarы: самого спринга, необходимые для поддержки web, hibernate и тп.
Исходный код в целом аналогичен преведущим примерам: в контексте присутствует два datasource, настроенные через jndi. Присутствуют hibernate мэппинг, dao, serice layer. Декларативное определение транзакций также аналогично ранее приведенному примеру.
Обратите внимание на определение менеджера распределенных транзакций. Ранее мы говорили: мы должны использовать некую внешнюю систему для управления транзакциями. Но никаких конфигураций мы не делаем, благодаря spring. При инициализации spring автоматически найдет доступный менеджер через jndi — т.к. он занет стандартные jndi пути для всех популярных серверов, и последовательно их перебирает.

Дополнительно мы создаем web flow, используя mvc. Наш контроллер взывает сервис для работы с базами данных.
Как обычно, успешно отрабатывает первый запуск (открытие страницы), если страницу перезагрузить — получаем exception и откат транзакции.

Для запуска web sample


Создайте обе базы. Скопируйте и распакуйте JBoss5. Отредактируйте определения источников из примера и запишите их в server/default/deploy.
Упакуйте приложение в xa.wat и скопируйте WAR в deploy.
Запустите JBoss (bin/run) и, если все в порядке, откройте:
localhost:8080/xa/testxa.do

Andrew Romanenco
andrew@romanenco.com
www.romanenco.com/springtx

You can download sample code from sf.net:

sourceforge.net/project/showfiles.php?group_id=220231&package_id=326183&release_id=689145
Tags:
Hubs:
Total votes 7: ↑6 and ↓1+5
Comments5

Articles