- PVSM.RU - https://www.pvsm.ru -
Всем добрый день!
Что ж, конец месяца у нас всегда интенсивные, вот и тут остался всего день до старта второго потока курса «Разработчик на Spring Framework» [1] — замечательного и интересного курса, который ведёт не менее прекрасный и злой Юрий [2](как его называют некоторые студент за уровень требований в ДЗ), так что давайте рассмотрим ещё один материал, который мы подготовили для вас.
Поехали.
Введение
Большую часть времени разработчики не придают значения управлению транзакциями. В результате либо большую часть кода приходится переписывать позже, либо разработчик реализует управление транзакциями без знаний того, как оно на самом деле должно работать или какие аспекты необходимо использовать конкретно в их случае.
Важный аспект в управлении транзакциями — определение правильных границы транзакции, когда транзакция должна начинаться и когда заканчиваться, когда данные должны быть добавлены в БД и когда они должны быть откачены обратно (в случае возникновения исключения).

Самый важный аспект для разработчиков — это понять как реализовать управление транзакциями в приложении наилучшим образом. Поэтому давайте рассмотрим различные варианты.
Способы управления транзакциями
Транзакции могут управляться следующими способами:
1. Программное управление путем написания пользовательского кода
Это старый способ управления транзакциями.
EntityManagerFactory factory = Persistence.createEntityManagerFactory("PERSISTENCE_UNIT_NAME"); EntityManager entityManager = entityManagerFactory.createEntityManager();
Transaction transaction = entityManager.getTransaction()
try
{
transaction.begin();
someBusinessCode();
transaction.commit();
}
catch(Exception ex)
{
transaction.rollback();
throw ex;
}
Плюсы:
Минусы:
2. Использование Spring для управления транзакциями
Spring поддерживает два типа управления транзакциями
1. Программное управление транзакциями: Вы должны управлять транзакциями с помощью программирования. Это способ достаточно гибкий, но его сложно поддерживать.
2. Декларативное управление транзакциями: Вы отделяете управление транзакциями от бизнес-логики. Вы используете только аннотации в конфигурации на основе XML для управления транзакциями.
Мы настоятельно рекомендуем использовать декларативные транзакции. Если вы хотите узнать причины, тогда читайте дальше, иначе переходите сразу к разделу Декларативное управление транзакциями, если хотите реализовать этот вариант.
Теперь давайте рассмотрим каждый подход детально.
2.1. Программное управление транзакциями:
Фреймворк Spring предоставляет два средства для программного управления транзакциями.
a. Использование TransactionTemplate (рекомендовано командой Spring):
Давайте рассмотрим как можно реализовать этот тип на примере кода, представленного ниже (взято из документации Spring с некоторыми изменениями)
Обратите внимание, что фрагменты кода взяты из Spring Docs.
Файл Context Xml:
<!-- Initialization for data source -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/TEST"/>
<property name="username" value="root"/>
<property name="password" value="password"/>
</bean>
<!-- Initialization for TransactionManager -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- Definition for ServiceImpl bean -->
<bean id="serviceImpl" class="com.service.ServiceImpl">
<constructor-arg ref="transactionManager"/>
</bean>
Класс Service:
public class ServiceImpl implements Service
{
private final TransactionTemplate transactionTemplate;
// используйте инъекцию в конструктор чтобы предоставить PlatformTransactionManager
public ServiceImpl(PlatformTransactionManager transactionManager)
{
this.transactionTemplate = new TransactionTemplate(transactionManager);
}
// параметры транзакции здесь могут быть установлены явно, если это необходимо, обеспечивая больше контроля
// Также мы можем сделать это через xml файл
this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED); this.transactionTemplate.setTimeout(30); //30 секунд
/// и так далее
public Object someServiceMethod()
{
return transactionTemplate.execute(new TransactionCallback()
{
// код в этом методе выполняется в транзакционном контексте
public Object doInTransaction(TransactionStatus status)
{
updateOperation1();
return resultOfUpdateOperation2();
}
});
}}
Если нет возвращаемого значения, используйте удобный класс TransactionCallbackWithoutResult с анонимным классом, как показано ниже:
transactionTemplate.execute(new TransactionCallbackWithoutResult()
{
protected void doInTransactionWithoutResult(TransactionStatus status)
{
updateOperation1();
updateOperation2();
}
});
TransactionTemplate потокобезопасные, поэтому поддерживают не все диалоговые состояния.TransactionTemplate тем не менее поддерживают конфигурационное состояние, поэтому, если классу необходимо использовать TransactionTemplate с разными настройками (например, другой уровень изоляции), то вам нужно создать два различных экземпляра TransactionTemplate, хотя в некоторых классах может использоваться один экземпляр TransactionTemplate.
b. Использование реализации PlatformTransactionManager напрямую:
Давайте снова посмотрим на эту опцию в коде.
<!-- Initialization for data source -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/TEST"/>
<property name="username" value="root"/>
<property name="password" value="password"/>
</bean>
<!-- Initialization for TransactionManager -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
public class ServiceImpl implements Service
{
private PlatformTransactionManager transactionManager;
public void setTransactionManager( PlatformTransactionManager transactionManager)
{
this.transactionManager = transactionManager;
}
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// Явное указание имени транзакции - это то, что может быть сделано только программно
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try
{
// выполняем вашу бизнес-логику здесь
}
catch (Exception ex)
{
txManager.rollback(status);
throw ex;
}
txManager.commit(status);
}
Теперь, перед тем как переходить к следующему способу управления транзакциями, давайте посмотрим как определиться, какой из типов управления транзакциями выбрать.
Выбор между Программным и Декларативным управлением транзакциями:
2.2. Декларативные транзакции (Обычно используется почти во всех сценариях любого веб-приложения)
Шаг 1: Определите менеджер транзакций в контекстном xml файле вашего spring-приложения.
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"/>
<tx:annotation-driven transaction-manager="txManager"/>
Шаг 2: Включите поддержку аннотаций, добавив запись в контекстном xml файле вашего spring-приложения.
ИЛИ добавьте @EnableTransactionManagement в ваш конфигурационный файл, как показано ниже:
@Configuration
@EnableTransactionManagement
public class AppConfig
{
...
}
Spring рекомендует аннотировать только конкретные классы (и методы конкретных классов) с аннотацией @Transactional в сравнении с аннотирующими интерфейсами.
Причина этого заключается в том, что вы помещаете аннотацию на уровень интерфейса, и если вы используете прокси-классы (proxy-target-class = «true») или сплетающий аспект (mode = «aspectj»), тогда параметры транзакции не распознаются инфраструктурой проксирования и сплетения, например Транзакционное поведение не будет применяться.
Шаг 3: Добавьте аннотацию @Transactional в класс (метод класса) или интерфейс (метод интерфейса).
<tx:annotation-driven proxy-target-class="true">
Конфигурация по умолчанию: proxy-target-class="false"
@Transactional может быть помещена перед определением интерфейса, метода интерфейса, определением класса или публичным методом класса.@Transactional) имели разные настройки атрибутов, такие как уровень изоляции или уровень распространения, разместите аннотацию на уровне метода, чтобы переопределить настройки атрибутов уровня класса.@Transactional.
Теперь давайте разберем разницу между атрибутами аннотации @Transactional
@Transactional (isolation=Isolation.READ_COMMITTED)
Isolation.DEFAULTtx), что для текущего tx должен использоваться следующий уровень изоляции. Должен быть установлен в точке, откуда начинается tx, потому что мы не можем изменить уровень изоляции после запуска tx.DEFAULT: Использовать уровень изоляции установленный по умолчанию в базовой базе данных.
READ_COMMITTED (чтение фиксированных данных): Постоянная, указывающая, что “грязное” чтение предотвращено; могут возникать неповторяющееся чтение и фантомное чтение.
READ_UNCOMMITTED (чтение незафиксированных данных): Этот уровень изоляции указывает, что транзакция может считывать данные, которые еще не удалены другими транзакциями.
REPEATABLE_READ (повторяемость чтения): Постоянная, указывающая на то, что “грязное” чтение и неповторяемое чтение предотвращаются; может появляться фантомное чтение.
SERIALIZABLE (упорядочиваемость): Постоянная, указывающая, что “грязное” чтение, неповторяемое чтение и фантомное чтение предотвращены.
Что означают эти жаргонизмы: “грязное” чтение, фантомное чтение или повторяемое чтение?
@Transactional(timeout=60)
По умолчанию используется таймаут, установленный по умолчанию для базовой транзакционной системы.
Сообщает менеджеру tx о продолжительности времени, чтобы дождаться простоя tx, прежде чем принять решение об откате не отвечающих транзакций.
@Transactional(propagation=Propagation.REQUIRED)
Если не указано, распространяющееся поведение по умолчанию — REQUIRED.
Другие варианты: REQUIRES_NEW, MANDATORY, SUPPORTS, NOT_SUPPORTED, NEVER и NESTED.
REQUIRED
Указывает, что целевой метод не может работать без активного tx. Если tx уже запущен до вызова этого метода, то он будет продолжаться в том же tx, или новый tx начнется вскоре после вызова этого метода.
REQUIRES_NEW
MANDATORY
SUPPORTS
NOT_SUPPORTED
NEVER
@Transactional (rollbackFor=Exception.class)
Значение по умолчанию: rollbackFor=RunTimeException.class
В Spring все классы API бросают RuntimeException, это означает, что если какой-либо метод не выполняется, контейнер всегда откатывает текущую транзакцию.
Проблема заключается только в проверенных исключениях. Таким образом, этот параметр можно использовать для декларативного отката транзакции, если происходит Checked Exception.
@Transactional (noRollbackFor=IllegalStateException.class)
Указывает, что откат не должен происходить, если целевой метод вызывает это исключение.
Теперь последним, но самым важным шагом в управлении транзакциями является размещение аннотации @Transactional. В большинстве случаев возникает путаница, где должна размещаться аннотация: на сервисном уровне или на уровне DAO?
@Transactional: Сервисный или DAO уровень?
Сервис — лучшее место для размещения @Transactional, сервисный уровень должен содержать поведение варианта использования на уровне детализации для взаимодействия пользователя, которое логически переходит в транзакцию.
Существует много CRUD-приложений, у которых нет существенной бизнес-логики, имеющих сервисный уровень, который просто передает данные между контроллерами и объектами доступа к данным, что не является полезным. В этих случаях мы можем поместить аннотацию транзакции на уровень DAO.
Поэтому на практике вы можете поместить их в любом месте, это зависит от вас.
Кроме того, если вы поместите @Transactional в уровень DAO и если ваш уровень DAO будет повторно использоваться разными службами, тогда будет сложно разместить его на уровне DAO, так как разные службы могут иметь разные требования.
Если ваш сервисный уровень извлекает объекты с помощью Hibernate, и, допустим, у вас есть ленивые инициализации в определении объекта домена, тогда вам нужно открыть транзакцию на сервисном уровне, иначе вам придется столкнуться с LazyInitializationException, брошенным ORM.
Рассмотрим другой пример, когда ваш уровень обслуживания может вызывать два разных метода DAO для выполнения операций БД. Если ваша первая операция DAO завершилась неудачей, остальные две могут быть переданы, и вы закончите несогласованное состояние БД. Аннотирование на сервисном уровне может спасти вас от таких ситуаций.
Надеюсь, эта статья вам помогла.
THE END
Всегда интересно увидеть ваш комментарии или вопросы.
Автор: MaxRokatansky
Источник [3]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/java/300662
Ссылки в тексте:
[1] «Разработчик на Spring Framework»: https://otus.pw/8LJd/
[2] Юрий : https://otus.pw/3AnN/
[3] Источник: https://habr.com/post/431508/?utm_campaign=431508
Нажмите здесь для печати.