Управление транзакциями, commit rollback
Транзакция Transaction включает одно или несколько изменений в базе данных, которые после выполнения либо все фиксируются (commit), либо все откатываются назад (rollback). При вызове метода commit или rollback текущая транзакция заканчивается и начинается другая.
По умолчанию каждое новое соединение находится в режиме автофиксации (autocommit = true). Это означает автоматическую фиксацию (commit) транзакции после выполнения каждого запроса. В этом случае транзакция включает только одно изменение (один запрос).
Если autocommit запрещен, т.е. равен false, то транзакция не заканчивается до явного вызова commit или rollback, включая, таким образом, все выражения, выполненные с момента последнего вызова commit или rollback. В этом случае все SQL-запросы в транзакции фиксируются или откатываются группой.
Метод фиксации commit завершает все изменения в БД, проделанные SQL-выражением, и снимает также все блокировки, установленные транзакцией. Метод rollback наоборот — не сохранит изменения и восстановит исходное состояние на момент начала транзакции.
Иногда пользователю нужно, чтобы какое-либо изменение не вступило в силу до тех пор, пока не вступит в силу предыдущее изменение. Этого можно достичь запрещением autocommit и группировкой обоих запросов в одну транзакцию. Если оба изменения произошли успешно, то вызывается метод commit, который переносит эффект от этих изменений в БД; если одно или оба запроса не прошли, то вызывается метод rollback, который возвращает прежнее состояние БД.
Большинство JDBC-драйверов поддерживают транзакции. В действительности драйвер, соответствующий спецификации JDBC, обязан поддерживать их. Интерфейс DatabaseMetaData позволяет получить информацию об уровнях изолированности транзакций, которые поддерживаются данной СУБД.
Пример использования транзакции — commit, autocommit
Connection connection = . ; // Сброс автофиксации connection.setAutoCommit(false); // Первая транзакция PreparedStatement updateSales = connection.prepareStatement( "UPDATE COFFEES SET SALES = ? WHERE COF_NAME LIKE ?"); updateSales.setInt(1, 50); updateSales.setString(2, "Colombian"); updateSales.executeUpdate(); // Вторая транзакция PreparedStatement updateTotal = connection.prepareStatement( "UPDATE COFFEES SET TOTAL = TOTAL + ? WHERE COF_NAME LIKE ?"); updateTotal.setInt(1, 50); updateTotal.setString(2, "Colombian"); updateTotal.executeUpdate(); // Завершение транзакции connection.commit(); // Восстановление по умолчанию connection.setAutoCommit(true);
В примере для соединения Connection режим автофиксации отключен и два оператора updateSales и updateTotal будут зафиксированы вместе при вызове метода commit.
В последней строке примера режим автофиксации autocommit восстанавливается. То есть, каждый следующий запрос опять будет фиксироваться автоматически после своего завершения.
Желательно запрещать режим автофиксации только тогда, когда в транзакции участвуют данные из нескольких таблиц, чтобы связанные данные нескольких таблиц либо были все записаны, либо отменены.
Уровни изолированности транзакций, dirty read
Транзакции не только обеспечивают полное завершение или откат операторов, которые они охватывают, но также изолируют данные. Уровень изоляции описывает степень видимости измененных данных для других транзакций.
Допустим, что один из пользователей обновляет параметры заказчика (адрес, телефон, email) и программа требует подтверждения выполнения транзакции. В это же время другой пользователь читает информацию из базы данных о данном заказчике. Прочитает ли второй пользователь новые и не подтвержденные данные, или будет читать старые? Ответ зависит от уровня изоляции транзакции. Если транзакция разрешает другим программам читать не подтвержденные данные, то другая программа не будет ожидать окончания транзакции. Но здесь возникает компромисс — если транзакция будет отменена, то вторая другая программа может прочитать ошибочные данные.
Есть несколько способов разрешения конфликтов между одновременно выполняющимися транзакциями. Разработчик может определить уровень изолированности так, что пока одна транзакция изменяет какое-либо значение, вторая транзакция могла бы прочитать обновленное значение до того, пока первая не выполнит commit или rollback. Для этого следует установить уровень изолированности TRANSACTION_READ_UNCOMMITTED:
Connection connection; . connection.setTransactionIsolation(TRANSACTION_READ_UNCOMMITTED);
В данном коде серверу указано на возможность чтения измененных значений до того, как выполнится commit, т.е. определена возможность «грязного чтения» («dirty read«).
По умолчанию уровень изоляции транзакций обычно установлен в READ_COMMITED.
Изменение уровня изолированности во время транзакции нежелательно, так как произойдет автоматический вызов commit, что повлечет за собой фиксацию изменений.
В связи с тем, что уровни изоляции, предлагаемые различными поставщиками СУБД, могут меняться, Вам следует обратиться к документации за дополнительной информацией. Уровни изоляции не стандартизованы для платформы J2EE.
Чем выше уровень изолированности транзакций, тем больше внимания СУБД уделяет устранению конфликтов. Интерфейс Connection определяет пять таких уровней. Минимальный из них соответствует случаю, когда транзакции не поддерживаются вовсе, а максимальный — невозможности существования более одной транзакции в любой момент времени.
Обычно, чем выше уровень изолированности, тем медленнее выполняется приложение (из-за избыточной блокировки). При выборе конкретного уровня изолированности разработчик должен найти золотую середину между потребностями в производительности и требованиями к целостности данных. Очевидно, что реально поддерживаемые уровни зависят от возможностей используемой СУБД.
При создании объекта Connection уровень его изолированности зависит от драйвера или БД. Можно вызвать метод setIsolationLevel, чтобы изменить уровень изолированности транзакций, и новое значение уровня будет установлено до конца сессии. Чтобы установить уровень изолированности только для одной транзакции, надо установить его перед выполнением транзакции и восстановить прежнее значение после ее завершения.
Типы уровней изолированности
- TRANSACTION_NONE
Транзакции не поддерживаются. - TRANSACTION_READ_COMMITTED
Запрет на «грязное чтение» (dirty read). Данный уровень блокирует транзакциям чтение строк с неподтвержденными изменениями в них. - TRANSACTION_READ_UNCOMMITTED
Разрешение на «dirty read». Данный уровень позволяет изменять строку с помощью одной транзакции и прочесть ее другой прежде, чем изменения в этой строке будут подтверждены (dirty read). Если изменения будут отменены с помощью rollback(), вторая транзакция вернет неправильную строку. - TRANSACTION_REPEATABLE_READ
Запрет на «dirty read». Данный уровень препятствует транзакции от чтения строки с неподтвержденным изменением в ней, он также предотвращает ситуацию, когда одна транзакция читает строку, а вторая транзакция изменяет ее, при этом первая транзакция перечитывая строку, получает разные значения каждый раз (разовое чтение). - TRANSACTION_SERIALIZABLE
Запрет на «dirty read». Данный уровень включает предотвращения из TRANSACTION_REPEATABLE_READ, более того предотвращает ситуацию, когда одна транзакция читает все строки, которые удовлетворяют условию WHERE, а вторая транзакция вставляет строку, которая удовлетворяет тому же условию WHERE, и первая транзакция, перечитывая с тем же условием, получает дополнительную «фантомную» строку при втором чтении.
Что такое транзакция java
В приложении Java EE транзакция представляет собой последовательность действий, которые либо должны все успешно завершиться, либо все изменения каждого действия должны быть отменены. Транзакции заканчиваются фиксацией (коммитом) или откатом.
Java Transaction API (JTA) позволяет приложениям получать доступ к транзакциям способом, который не зависит от конкретных реализаций. JTA определяет стандартные интерфейсы Java между менеджером транзакций и сторонами, участвующими в системе распределённых транзакций: транзакционным приложением, сервером Java EE и менеджером, который контролирует доступ к общим ресурсам, на которые влияют транзакции.
JTA определяет интерфейс UserTransaction , который приложения используют для запуска, фиксации или отката транзакций. Компоненты приложения получают объект UserTransaction через поиск JNDI, используя имя java:comp/UserTransaction или инъецируя объект UserTransaction . Сервер приложений использует несколько JTA-определённых интерфейсов для связи с менеджером транзакций. Менеджер транзакций использует JTA-определённые интерфейсы для взаимодействия с менеджером ресурсов.
Спецификация JTA 1.2 доступна по ссылке http://jcp.org/en/jsr/detail?id=907 .
Назад | Вперёд |
Copyright © 2017, Oracle и/или её дочерних компаний. Все права защищены. Версия перевода 1.0.5 (Java EE Tutorial — русскоязычная версия)
Основы транзакций в Spring и JDBC
В этой статье мы разберемся, что такое транзакции. Какими обладают транзакции — ACID. Как транзакции выполняются на уровне JDBС, а также на уровне Spring.
12 окт. 2022 · 7 минуты на чтение
Мы начнём изучение транзакций с нуля, шаг за шагом погружаясь в тему. Быстро рассмотрим проблему, которую решают транзакции. Далее посмотрим, как написать транзакцию на SQL. А потом разберёмся с управлением транзакциями в JDBC. Всё, что делает Spring, основано на JDBC. И вы сэкономите кучу времени при работе с аннотацией @Transactional , если усвоите эти основы.
Спонсор поста
Переведенная статья
Данная статья является переводом и адаптацией англоязычной статьи. Я тщательно перевожу статью на русский, актуализирую устаревшие моменты, а также проверяю или пишу примеры кода, которые вы можете запустить самостоятельно.
– – – –
Spring Transaction Management: @Transactional In-Depth
Проблематика
Что такое транзакция и зачем она нужна проще объяснить на примере. Допустим, Вася переводит Пете 100 рублей. Для выполнения этой бизнес-функции нам потребуется три действия:
- Списать деньги с баланса Васи;
- Записать операцию перевода от Васи к Пете;
- Добавить денег на баланс Пете;
Представим, что мы списали деньги с баланса Васи, а потом произошла ошибка. В итоге на балансе у Васи нет денег, а у Пети они не появились. Приятная ситуация для банка, но неприятная для Васи и Пети.
Для решения таких проблем и существуют транзакции. Транзакция позволит нам объединить эти операции таким образом, что по итогу мы либо запишем все изменения, либо ничего. То есть объединить несколько различных действий в атомарную операцию.
Свойства транзакций ACID
Транзанкции обладют свойствами, которые называют ACID:
Атомарность (Atomicity) гарантирует, что никакая транзакция не будет зафиксирована в системе частично. Будут либо выполнены все операции, либо ни одной.
Согласованность (Consistency). Выполненая транзакция, сохраняет согласованность базы данных. Согласованность является более широким понятием, чем может показаться.
Например, в банковской системе может существовать требование равенства суммы, списываемой с одного счёта, сумме, зачисляемой на другой. Это бизнес-правило и оно не может быть гарантировано только проверками целостности базы данных. Это поведение должны учитывать программисты при написании кода транзакций. Если какая-либо транзакция произведёт списание, но не произведёт зачисления, то система останется в некорректном состоянии и свойство согласованности будет нарушено.
Изолированность (Isolation). Во время выполнения транзакции параллельные транзакции не должны оказывать влияние на её результат.
Изолированность обходится дорого, поэтому в реальных базах данных существуют режимы, не полностью изолирующие транзакцию. Об уровнях изоляции мы поговорим в отдельной статье.
Устойчивость (Durability). Независимо от проблем с оборудованием, изменения, сделанные успешно завершённой транзакцией, должны остаться сохранёнными после возвращения системы в работу.
Транзакции в SQL
Коротко рассмотрим, как сделать транзакцию в SQL. Для этого используют ключевые слова BEGIN , COMMIT , ROLLBACK .
Возвращаясь к примеру с переводами, представим, что у нас есть 2 таблицы:
И мы хотим сделать перевод между первым и вторым пользователем 2. Других пользователей у нас в системе нет.
UPDATE person SET balance = (balance - 10) WHERE INTO transaction(person_from, person_to, amount) values (1, 3, 10); UPDATE person SET balance = (balance + 10) WHERE >Мы уменьшаем баланс первого пользователя, потом создаём запись в таблице переводов, но случайно ошибаемся и записываем перевод несуществующему третьему пользователю. После чего пытаемся обновить баланс второго пользователя.
Между таблицами transaction и person есть связь, а пользователя с идентификатором 3 не существует, поэтому мы не сможем выполнить INSERT . И дальнейшее выполнение кода будет остановлено, то есть у первого пользователя баланс уменьшается, а у второго не увеличивается.
Исправим эту ситуацию с помощью транзакции:
BEGIN; UPDATE person SET balance = (balance - 10) WHERE INTO transaction(person_from, person_to, amount) values (1, 3, 10); UPDATE person SET balance = (balance + 10) WHERE >Мы обернули код в команды BEGIN и COMMIT . Но теперь, когда произойдёт ошибка в операторе INSERT , изменения, внесенные первым оператором UPDATE , не будут сохранены в базу данных. Таким образом, все три изменения будут записаны либо вместе, либо не будут записаны вовсе. А теперь переходим к родному для нас JDBC.
Учтите, что после ошибки в транзакции необходимо вызвать оператор ROLLBACK . Для упрощения в примере этот момент опущен.
Управление транзакциями в JDBC
Первое, что вам стоит понять и запомнить: не имеет значения, используете ли вы аннотацию @Transactional от Spring, Hibernate, jOOQ или любую другую библиотеку для работы с базой данных. В конечном счёте все они делают одно и то же — открывают и закрывают транзакции базы данных.
Обычный код управления транзакциями JDBC выглядит следующим образом:
import java.sql.Connection; Connection connection = dataSource.getConnection(); // (1) try (connection) < connection.setAutoCommit(false); // (2) // execute some SQL statements. connection.commit(); // (3) >catch (SQLException e) < connection.rollback(); // (4) >
- Для запуска транзакций вам необходимо подключение к базе данных. DriverManager.getConnection(url, user, password) тоже подойдёт, хотя в большинстве корпоративных приложений вы будете иметь настроенный источник данных и получать соединения из этого источника.
- Это единственный способ начать транзакцию базы данных в Java, возможно, звучит немного странно. Вызов setAutoCommit(true) гарантирует, что каждый SQL-оператор будет автоматически завёрнут в собственную транзакцию, а setAutoCommit(false) — наоборот. Теперь вы должны управлять жизнью транзакции. Обратите внимание, что флаг autoCommit действует в течение всего времени, пока соединение открыто.
- Фиксируем транзакцию
- Или откатываем изменения, если возникло исключение.
Управление транзакциями в Spring
Поскольку теперь у вас есть понимание транзакций JDBC, посмотрим, как Spring управляет транзакциями. Это также применимо и к Spring Boot и Spring MVC.
В обычном JDBC у вас есть один способ ( setAutocommit(false) ) управлять транзакциями, Spring предлагает вам множество различных, более удобных способов добиться того же самого.
Программное управление транзакциями Spring
Первый, но довольно редко используемый способ внедрения транзакций – программный. Либо через TransactionTemplate , либо через PlatformTransactionManager . Это выглядит следующим образом:
@Service public class UserService < @Autowired private TransactionTemplate template; public Long registerUser(User user) < Long ->< // execute some SQL that e.g. // inserts the user into the db and returns the autogenerated id return id; >); > >
По сравнению с обычным примером JDBC:
- Вам не нужно открывать и закрывать соединений с базой данных самостоятельно. Вместо этого, вы используете Transaction Callbacks.
- Также не нужно ловить SQLExceptions , так как Spring преобразует их в исключения времени выполнения.
- Кроме того, вы лучше интегрируетесь в экосистему Spring. TransactionTemplate будет использовать TransactionManager внутри, который будет использовать источник данных. Всё это бины, которые выукажете в конфигурации, но о которых вам больше не придется беспокоиться в дальнейшем.
Хотя это, и выглядит небольшим улучшением по сравнению с JDBC, программное управление транзакциями — это не самый лучший способ. Вместо этого, используйте декларативное управление транзакциями.
Декларативное управление транзанкциями
Посмотрим, как обычно выглядит управление транзакциями в Spring:
public class UserService < @Transactional public Long registerUser(User user) < // execute some SQL that e.g. // inserts the user into the db and retrieves the autogenerated id // userDao.save(user); return id; >>
Никакого лишнего «технического» кода. Вместо этого, нужно сделать две вещи:
- Убедиться, что одна из Spring конфигурации аннотирована @EnableTransactionManagement . В SpringBoot даже этого делать не нужно.
- Убедиться, что вы указали менеджер транзакций в конфигурации. Это делается в любом случае.
И тогда Spring будет обрабатывать транзакции за вас. Любой публичный метод бина, который вы аннотируете @Transactional , будет выполняться внутри транзакции базы данных.
Итак, чтобы аннотация @Transactional заработала, делаем следующее:
@Configuration @EnableTransactionManagement public class MySpringConfig < @Bean public PlatformTransactionManager txManager() < return yourTxManager; // more on that later >>
Вооружившись знаниями из примера про транзакций JDBC, приведенный выше код можно упрощенно представить следующим образом:
Как видно из этой схемы, у прокси следующие задачи:
- открытие и закрытие соединений/транзакций с базой данных;
- А затем делегирование выполнения настоящему UserService , тому, который вы написали;
- А другие бины, такие как ваш UserRestController , никогда не узнают, что они работают с прокси, а не с настоящим сервисом;
Для чего нужен менеджер транзакций?
Теперь не хватает только одной важной части информации, хотя мы уже несколько раз упоминали о ней.
Ваш UserService проксируется во время выполнения, и прокси управляет транзакциями. Но не сам прокси управляет всем транзакционным состоянием, прокси делегирует работу менеджеру транзакций.
Spring предлагает вам интерфейс PlatformTransactionManager / TransactionManager , который по умолчанию поставляется с парой удобных реализаций. Одна из них – DataSourceTransactionManager .
Он делает то же самое, что вы делали до сих пор для управления транзакциями, но сначала рассмотрим необходимую конфигурацию Spring:
@Bean public DataSource dataSource() < return new MysqlDataSource(); // (1) >@Bean public PlatformTransactionManager txManager() < return new DataSourceTransactionManager(dataSource()); // (2) >
- Создаем источник базы данных. В этом примере используется MySQL.
- Создаем менеджер транзакций, которому нужен источник данных, чтобы иметь возможность управлять транзакциями.
Все менеджеры транзакций имеют такие методы, как doBegin() или doCommit() , которые выглядят следующим образом:
public class DataSourceTransactionManager implements PlatformTransactionManager < @Override protected void doBegin(Object transaction, TransactionDefinition definition) < Connection newCon = obtainDataSource().getConnection(); // . con.setAutoCommit(false); // yes, that's it! >@Override protected void doCommit(DefaultTransactionStatus status) < // . Connection connection = status.getTransaction().getConnectionHolder().getConnection(); try < con.commit(); >catch (SQLException ex) < throw new TransactionSystemException("Could not commit JDBC transaction", ex); >> >
Таким образом, менеджер транзакций источника данных использует при управлении транзакциями такой же код, который вы видели в разделе про JDBC.
Учитывая это, улучшим схему:
- Если Spring обнаруживает аннотацию @Transactional на бине, он создаёт динамический прокси этого бина.
- Прокси имеет доступ к менеджеру транзакций и будет просить его открывать и закрывать транзакции/соединения.
- Сам менеджер транзакций будет просто управлять старым добрым соединением JDBC.
Резюмирую
К этому моменту вы должны иметь довольно хорошее представление о том, что такое транзакция. Как она выглядит в SQL. И как управление транзакциями работает с фреймворком Spring.
Главным выводом должно быть то, что в итоге не имеет значения, какой фреймворк вы используете, всё дело в основах JDBC.
Транзакции не всегда изолированы друг от друга, поэтому две параллельные транзакции, которые работают с одними и теми же данными, могут оказывать влияние друг на друга. Подробнее об этом читайте в следующей статье
Транзакции в Spring
Есть две стратегии управления: программное управление (пишем код вручную) и декларативное (настраиваем границы транзакций в 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
You can download sample code from sf.net: