что такое транзакция java
Управление транзакциями, commit rollback
Транзакция Transaction включает одно или несколько изменений в базе данных, которые после выполнения либо все фиксируются (commit), либо все откатываются назад (rollback). При вызове метода commit или rollback текущая транзакция заканчивается и начинается другая.
По умолчанию каждое новое соединение находится в режиме автофиксации (autocommit = true). Это означает автоматическую фиксацию (commit) транзакции после выполнения каждого запроса. В этом случае транзакция включает только одно изменение (один запрос).
Если autocommit запрещен, т.е. равен false, то транзакция не заканчивается до явного вызова commit или rollback, включая, таким образом, все выражения, выполненные с момента последнего вызова commit или rollback. В этом случае все SQL-запросы в транзакции фиксируются или откатываются группой.
Иногда пользователю нужно, чтобы какое-либо изменение не вступило в силу до тех пор, пока не вступит в силу предыдущее изменение. Этого можно достичь запрещением autocommit и группировкой обоих запросов в одну транзакцию. Если оба изменения произошли успешно, то вызывается метод commit, который переносит эффект от этих изменений в БД; если одно или оба запроса не прошли, то вызывается метод rollback, который возвращает прежнее состояние БД.
Большинство JDBC-драйверов поддерживают транзакции. В действительности драйвер, соответствующий спецификации JDBC, обязан поддерживать их. Интерфейс DatabaseMetaData позволяет получить информацию об уровнях изолированности транзакций, которые поддерживаются данной СУБД.
В примере для соединения Connection режим автофиксации отключен и два оператора updateSales и updateTotal будут зафиксированы вместе при вызове метода commit.
В последней строке примера режим автофиксации autocommit восстанавливается. То есть, каждый следующий запрос опять будет фиксироваться автоматически после своего завершения.
Желательно запрещать режим автофиксации только тогда, когда в транзакции участвуют данные из нескольких таблиц, чтобы связанные данные нескольких таблиц либо были все записаны, либо отменены.
Уровни изолированности транзакций, dirty read
Транзакции не только обеспечивают полное завершение или откат операторов, которые они охватывают, но также изолируют данные. Уровень изоляции описывает степень видимости измененных данных для других транзакций.
Есть несколько способов разрешения конфликтов между одновременно выполняющимися транзакциями. Разработчик может определить уровень изолированности так, что пока одна транзакция изменяет какое-либо значение, вторая транзакция могла бы прочитать обновленное значение до того, пока первая не выполнит commit или rollback. Для этого следует установить уровень изолированности TRANSACTION_READ_UNCOMMITTED:
В данном коде серверу указано на возможность чтения измененных значений до того, как выполнится commit, т.е. определена возможность «грязного чтения» («dirty read«).
По умолчанию уровень изоляции транзакций обычно установлен в READ_COMMITED.
Изменение уровня изолированности во время транзакции нежелательно, так как произойдет автоматический вызов commit, что повлечет за собой фиксацию изменений.
В связи с тем, что уровни изоляции, предлагаемые различными поставщиками СУБД, могут меняться, Вам следует обратиться к документации за дополнительной информацией. Уровни изоляции не стандартизованы для платформы J2EE.
Обычно, чем выше уровень изолированности, тем медленнее выполняется приложение (из-за избыточной блокировки). При выборе конкретного уровня изолированности разработчик должен найти золотую середину между потребностями в производительности и требованиями к целостности данных. Очевидно, что реально поддерживаемые уровни зависят от возможностей используемой СУБД.
При создании объекта Connection уровень его изолированности зависит от драйвера или БД. Можно вызвать метод setIsolationLevel, чтобы изменить уровень изолированности транзакций, и новое значение уровня будет установлено до конца сессии. Чтобы установить уровень изолированности только для одной транзакции, надо установить его перед выполнением транзакции и восстановить прежнее значение после ее завершения.
Документация разработчика Hibernate – Глава II. Транзакции и контроль многопоточности
Перевод статьи актуален для версии Hibernate 4.2.19.Final
2.1. Определение транзакции
2.2. Физические транзакции
Hibernate использует JDBC API для персистентности. В мире Java есть два хорошо определенных механизма работы с транзакциями: непосредственно JDBC и JTA. Hibernate поддерживает оба механизма интеграции с транзакциями и позволяет приложениям управлять физическими транзакциями.
org.hibernate.engine.transaction.spi.TransactionFactory – стандартный сервис Hibernate. Cм. Подробности в секции 7.5.16, “org.hibernate.engine.transaction.spi.TransactionFactory”.
2.2.1. Физические транзакции — JDBC
Управление транзакциями при помощи JDBC достигается методами java.sql.Connection.commit() и java.sql.Connection.rollback() (JDBC не определяет явного метода для инициирования транзакции). В Hibernate, данный подход представлен классом org.hibernate.engine.transaction.internal.jdbc.JdbcTransactionFactory
2.2.2. Физические транзакции — JTA
JTA-подход к транзакциям достигается интерфейсом javax.transaction.UserTransaction, получаемым из API org.hibernate.service.jta.platform.spi.JtaPlatform. Этот подход представлен классом org.hibernate.engine.transaction.internal.jta.JtaTransactionFactory
См. по интеграции с JTA Секция 7.5.9,“org.hibernate.service.jta.platform.spi.JtaPlatform”
2.2.3. Физические транзакции — CMT
2.2.4. Физические транзакции — Прочее
2.2.5. Физические транзакции — Устаревшее
2.3. Применение транзакций Hibernate
Hibernate использует соединения JDBC и ресурсы JTA напрямую, без дополнительной логики синхронизации. Для вас важно ознакомится с JDBC, ANSI SQL, и спецификой изоляции транзакций в вашей СУБД.
Hibernate не проводит синхронизацию на объектах в памяти. Поведение, определенное уровнем изоляции ваших БД-транзакций не меняется, когда вы используете Hibernate. Объект org.hibernate.Session выступает как предоставляющий повторяющиеся чтения (repeatable reads) и запросы кэш, ограниченный пределами транзакции.
2.4. Паттерны и анти-паттерны транзакций
2.4.1. Анти-паттерн сессия-на-операцию
2.4.2. Паттерн сессия-на-запрос
2.4.3. Диалоги
Паттерн сессия-на-запрос не является единственным средством дизайна unit of work. Множество бизнес-процессов требуют всей серии взаимодействий с пользователем, которые чередуются с доступом к БД. В веб- и энтерпрайз- приложениях, неприемлемо для транзакции БД охватывать все пользовательское взаимодействие. Рассмотрим следующий пример:
Даже, хотя мы и имеем несколько случаев доступа к БД, с точки зрения пользователя, данная серия шагов представляет одну единицу совершенной работы (Unit of Work). Есть множество путей реализации этого в приложении.
Первый (наивный) метод заключается в удержании открытыми сессий Session и транзакции на время редактирования пользователя, с использованием механизмов синхронизации БД для обеспечения эксклюзивного доступа пользователя к редактируемым данным, и предотвращению обращения к ним со стороны других пользователей, гарантируя изоляцию и атомарность. Это анти-паттерн, так как лишняя синхронизация является узким местом при проблемах производительности, встающих в высоконагруженных приложениях.
Ряд транзакций БД используется для реализации диалога с БД. В данном случае, обеспечение изоляции бизнес-процессов ложится на плечи приложения. Один диалог обычно покрывает несколько транзакций. Множественные доступы к БД могут быть атомарными, если только одна транзакция (обычно последняя) осуществляет запись в БД. Все другие только читают данные. Типичный путь реализации – через создание wizard-style диалога, покрывающего несколько шагов цикла запрос/ответ. Hibernate включает в себя некоторые возможности, позволяющие реализовать подобный функционал.
2.5. Идентичность объекта
Приложение может осуществлять параллельный доступ к одному и тому же persistent-состоянию (строка в базе данных) в двух разных сессиях. Однако, экземпляр persistent-класса никогда не разделяется между двумя разными сессиями. Имеют место быть и вступают в игру два разных понятия идентичности: БД-идентичность и JVM-идентичность.
Пример 2.1. БД-идентичность
Пример 2.2. JVM-идентичность
Для объектов, присоединенных к одной и той же сессии Session, два понятия идентичности эквивалентны, и JVM-идентичность гарантируется БД-идентичность Hibernate’ом. Приложение может параллельно осуществлять доступ к бизнес-объекту с одной и той же БД-идентичностью в двух разных сессиях, тем временем он будет представлен двумя разными экземплярами Java-объектов, в терминах JVM-идентичности. Разрешение конфликтов осуществляется оптимистичной стратегией и автоматическим версионированием во время сброса(flush)/коммита.
Этот подход возлагает ответственность за управлением параллельностью на Hibernate и БД. Он также обеспечивает лучшую масштабируемость, так как дорогие блокировки не нужны для гарантии идентичности в однопоточном unit of work (single-threaded unit of work). Приложению нет нужды синхронизироваться на каком бы то ни было бизнес-объекте, пока он работает в одном потоке. Хоть это и не рекомендуется, в пределах сессии Session приложение может безопасно использовать оператор == для сравнения объектов.
Однако, приложение, использующее оператор == за пределами сессии Session может внести некоторые проблемы. Если вы добавите два отсоединенных экземпляра объекта в один Set, они возможно будут иметь одну БД-идентичность, т.е они представляют одну и ту же строку в таблице. Совсем не гарантировано, что они будут иметь одну и ту же JVM-идентичность, будучи в состоянии detached. Переопределите методы equals() и hashCode() в persistent-классах, так что они будут иметь собственное определение объектной эквивалентности. Не используйте БД-идентичность для реализации проверки на равенство. Вместо этого, используйте бизнес-ключ, являющийся комбинацией уникальных, неизменяемых атрибутов. БД идентификатор может измениться, если объект перейдет из состояния transient в состояние persistent. Если transient экземпляр находится вместе с detached экземпляром в одном Set’e – изменение хэшкода нарушит контракт Set’а. Атрибуты для бизнес-ключа могут быть менее устойчивыми чем основные ключи. Вам только необходимо гарантировать стабильность до тех пор, пока объекты находятся в одном Set’е. Это не проблема Hibernate, так как относится к реализации объектной идентичности и эквивалентности в Java.
Руководство по JDBC. Транзакции.
Когда мы работаем с JDBC, то по умолчанию наше соединение работает в режиме auto-commit, это означает, что каждый SQL – запрос будет выполнен и результаты будут сохранены в таблице нашей базы данных (далее – БД).
Для простых приложений это крайне удобно. Но, если мы хотим увеличить производительность, использовать распределённые транзакции, либо интегрировать бизнес-логику, то нам необходимо выключить режим auto-commit для управления нашими транзакциями.
Транзакцию дают нам возможность контролировать когда и где сохранять изменения в БД. Благодаря этому мы, например, можем объединить группу SQL – запросов в одну логическую группу и, если один из запросов не пройдёт – мы отменяем всю транзакцию.
Для того чтобы получить доступ к управлению транзакциями, нам необходимо использовать метод setAutoCommit().
В коде это выглядит следующим образом:
Выполнение и откат (Commit and Rollback)
После того, как мы выполнили необходимые нам изменения, мы должны вызвать метод commit() таким образом:
Если же мы хотим выполнить откат изменений, то нам необходимо вызвать метод rollback():
Точки сохранения (Savepoints)
Начиниая со спецификации JDBC 3.0 интерфейс Savepoint даёт нам ещё больший контроль над транзакциями.
Когда мы используем savepoint, мы определяем точку, до которой произойдёт откат в случае, если нам понадобится отменить изменения.
Для управления этой функцией существует два метода:
Для понимания того, как это работает на практике рассмотрим пример простого приложения.
В результате работы программы мы получим, примерно, следующий результат:
Как мы видим, в этом приложении мы добавляем две записи с помощью двух SQL – запросов. Первый запрос корректен и выполняется без проблем. Второй же запрос содержит синтаксические ошибки и программа выдаёт нам SQLException.
Но метод commit() мы вызываем только после второго запроса. Поэтому, после выполнения отката, мы получаем таблицу в исходном виде, так как первый запрос также был отменён.
В этом уроке мы изучили основы управления транзакциями и рассмотрели пример с применением методов commit(), rollback() и использованием точки сохранения (savepoint).
В следующем уроке мы изучим исключения и их обработку при работе с JDBC.
Введение в транзакции в Java и Spring
1. Введение
В этом руководстве мы поймем, что подразумевается под транзакциями в Java. Таким образом, мы поймем, как выполнять локальные транзакции ресурсов и глобальные транзакции. Это также позволит нам изучить различные способы управления транзакциями в Java и Spring.
2. Что такое транзакция?
Кроме того, эти транзакции могут включать один или несколько ресурсов, таких как база данных, очередь сообщений, что приводит к различным способам выполнения действий в рамках транзакции. К ним относятся выполнение локальных транзакций ресурсов с отдельными ресурсами. В качестве альтернативы в глобальной транзакции могут участвовать несколько ресурсов.
3. Локальные транзакции с ресурсами
В Java у нас есть несколько способов доступа и работы с таким ресурсом, как база данных. Следовательно, мы работаем с транзакциями по-разному. В этом разделе мы узнаем, как мы можем использовать транзакции с некоторыми из этих библиотек в Java, которые довольно часто используются.
3.1. JDBC
Однако, если мы хотим объединить несколько операторов в одну транзакцию, этого также можно достичь:
3.2. JPA
Давайте посмотрим, как мы можем создать EntityManager и определить границу транзакции вручную:
3.3. JMS
Давайте посмотрим, как мы можем создать транзакционный сеанс для отправки нескольких сообщений в рамках транзакции:
4. Глобальные транзакции
4.1. JTA
Java Transaction API (JTA) is a Java Enterprise Edition API developed under the Java Community Process. It enables Java applications and application servers to perform distributed transactions across XA resources. JTA is modeled around XA architecture, leveraging two-phase commit.
JTA specifies standard Java interfaces between a transaction manager and the other parties in a distributed transaction:
Let’s understand some of the key interfaces highlighted above:
4.2. JTS
Java Transaction Service (JTS) is a specification for building the transaction manager that maps to the OMG OTS specification. JTS uses the standard CORBA ORB/TS interfaces and Internet Inter-ORB Protocol (IIOP) for transaction context propagation between JTS transaction managers.
At a high level, it supports the Java Transaction API (JTA). A JTS transaction manager provides transaction services to the parties involved in a distributed transaction:
Services that JTS provides to an application are largely transparent and hence we may not even notice them in the application architecture. JTS is architected around an application server which abstracts all transaction semantics from the application programs.
5. JTA Transaction Management
Now it’s time to understand how we can manage a distributed transaction using JTA. Distributed transactions are not trivial solutions and hence have cost implications as well. Moreover, there are multiple options that we can choose from to include JTA in our application. Hence, our choice must be in the view of overall application architecture and aspirations.
5.1. JTA in Application Server
As we have seen earlier, JTA architecture relies on the application server to facilitate a number of transaction-related operations. One of the key services it relies on the server to provide is a naming service through JNDI. This is where XA resources like data sources are bound to and retrieved from.
Apart from this, we have a choice in terms of how we want to manage the transaction boundary in our application. This gives rise to two types of transactions within the Java application server:
One of the main drawbacks of performing transactions in the context of an application server is that the application becomes tightly coupled with the server. This has implications with respect to testability, manageability, and portability of the application. This is more profound in microservice architecture where the emphasis is more on developing server-neutral applications.
5.2. JTA Standalone
The problems we discussed in the last section have provided a huge momentum towards creating solutions for distributed transactions that does not rely on an application server. There are several options available to us in this regard, like using transaction support with Spring or use a transaction manager like Atomikos.
Let’s see how we can use a transaction manager like Atomikos to facilitate a distributed transaction with a database and a message queue. One of the key aspects of a distributed transaction is enlisting and delisting the participating resources with the transaction monitor. Atomikos takes care of this for us. All we have to do is use Atomikos-provided abstractions:
Here, we are creating an instance of AtomikosDataSourceBean and registering the vendor-specific XADataSource. From here on, we can continue using this like any other DataSource and get the benefits of distributed transactions.
Similarly, we have an abstraction for message queue which takes care of registering the vendor-specific XA resource with the transaction monitor automatically:
Here, we are creating an instance of AtomikosConnectionFactoryBean and registering the XAConnectionFactory from an XA-enabled JMS vendor. After this, we can continue to use this as a regular ConnectionFactory.
Now, Atomikos provides us the last piece of the puzzle to bring everything together, an instance of UserTransaction:
Now, we are ready to create an application with distributed transaction spanning across our database and the message queue:
Here, we are using the methods begin and commit in the class UserTransaction to demarcate the transaction boundary. This includes saving a record in the database as well as publishing a message to the message queue.
6. Transactions Support in Spring
We have seen that handling transactions are rather an involved task which includes a lot of boilerplate coding and configurations. Moreover, each resource has its own way of handling local transactions. In Java, JTA abstracts us from these variations but further brings provider-specific details and the complexity of the application server.
Spring platform provides us a much cleaner way of handling transactions, both resource local and global transactions in Java. This together with the other benefits of Spring creates a compelling case for using Spring to handle transactions. Moreover, it’s quite easy to configure and switch a transaction manager with Spring, which can be server provided or standalone.
Spring provides us this seamless abstraction by creating a proxy for the methods with transactional code. The proxy manages the transaction state on behalf of the code with the help of TransactionManager:
The central interface here is PlatformTransactionManager which has a number of different implementations available. It provides abstractions over JDBC (DataSource), JMS, JPA, JTA, and many other resources.
6.1. Configurations
Let’s see how we can configure Spring to use Atomikos as a transaction manager and provide transactional support for JPA and JMS. We’ll begin by defining a PlatformTransactionManager of the type JTA:
Here, we are providing instances of UserTransaction and TransactionManager to JTATransactionManager. These instances are provided by a transaction manager library like Atomikos:
The classes UserTransactionImp and UserTransactionManager are provided by Atomikos here.
Further, we need to define the JmsTemplete which the core class allowing synchronous JMS access in Spring:
Here, ConnectionFactory is provided by Atomikos where it enables distributed transaction for Connection provided by it:
So, as we can see, here we are wrapping a JMS provider-specific XAConnectionFactory with AtomikosConnectionFactoryBean.
Next, we need to define an AbstractEntityManagerFactoryBean that is responsible for creating JPA EntityManagerFactory bean in Spring:
As before, the DataSource that we set in the LocalContainerEntityManagerFactoryBean here is provided by Atomikos with distributed transactions enabled:
Here again, we are wrapping the provider-specific XADataSource in AtomikosDataSourceBean.
6.2. Transaction Management
Having gone through all the configurations in the last section, we must feel quite overwhelmed! We may even question the benefits of using Spring after all. But do remember that all this configuration has enabled us abstraction from most of the provider-specific boilerplate and our actual application code does not need to be aware of that at all.
So, now we are ready to explore how to use transactions in Spring where we intend to update the database and publish messages. Spring provides us two ways to achieve this with their own benefits to choose from. Let’s understand how we can make use of them:
The easiest way to use transactions in Spring is with declarative support. Here, we have a convenience annotation available to be applied at the method or even at the class. This simply enables global transaction for our code:
The simple code above is sufficient to allow a save-operation in the database and a publish-operation in message queue within a JTA transaction.
While the declarative support is quite elegant and simple, it does not offer us the benefit of controlling the transaction boundary more precisely. Hence, if we do have a certain need to achieve that, Spring offers programmatic support to demarcate transaction boundary:
So, as we can see, we have to create a TransactionTemplate with the available PlatformTransactionManager. Then we can use the TransactionTemplete to process a bunch of statements within a global transaction.
7. Afterthoughts
As we have seen that handling transactions, particularly those that span across multiple resources are complex. Moreover, transactions are inherently blocking which is detrimental to latency and throughput of an application. Further, testing and maintaining code with distributed transactions is not easy, especially if the transaction depends on the underlying application server. So, all in all, it’s best to avoid transactions at all if we can!
But that is far from reality. In short, in real-world applications, we do often have a legitimate need for transactions. Although it’s possible to rethink the application architecture without transactions, it may not always be possible. Hence, we must adopt certain best practices when working with transactions in Java to make our applications better:
8. Conclusion
Подводя итог, в этом руководстве мы обсуждали транзакции в контексте Java. Мы прошли поддержку локальных транзакций отдельных ресурсов в Java для разных ресурсов. Мы также рассмотрели способы достижения глобальных транзакций на Java.
Далее мы рассмотрели разные способы управления глобальными транзакциями в Java. Также мы поняли, как Spring упрощает для нас использование транзакций в Java.
Наконец, мы рассмотрели некоторые из лучших практик при работе с транзакциями в Java.