Мало знаний, большой вызов! Эта статья участвует в "Необходимые знания для программистов«Творческая деятельность.
Не спешите отвечать первым, еще не поздно прочитать. Все мы знаем, что в проекте Spring мы можем напрямую использовать аннотацию @Transactional для идентификации метода транзакции. Однако вы можете не знать, выполняется ли транзакция так, как вы хотите. Давайте вместе рассмотрим несколько ситуаций.То, что вы думаете об управлении транзакциями, может не совпадать с вашим представлением об управлении транзакциями.
0 Классические случаи ошибок
@Transactional
void transfer() {
try{
//...
} catch (Exception e){
LOGGER.error(e.getMessage())
}
}
1 Выдает проверенное исключение
Если метод выдает проверенное исключение, такое как fileNotFound, транзакция не будет откатана.Причина очень проста, потому что rollbackFor по умолчанию аннотации @Transactional является исключением времени выполнения. Вот почему спецификация разработки Ali требует указания rollbackFor.
Поэтому, когда мы используем эту аннотацию, рекомендуется писать rollbackFor, чтобы вы выполняли откат, если точно знаете, какое исключение возникает.
@Transactional(rollbackFor = Exception.class)
2 Неправильное использование try catch
Бизнес-метод использует try catch для перехвата исключения, а затем исключение появляется плавно, результат попадает в ваш блок catch, но вы его не выбрасываете, и транзакция не откатывается корректно. Принцип, который появляется здесь, заключается в том, что Spring использует прокси для реализации управления транзакциями.Последовательность вызова состоит в том, чтобы запустить транзакцию, выполнить целевой метод и зафиксировать или откатить транзакцию.Хотя у вашего целевого метода есть исключение, вы не можете Сделай это сам С точки зрения прокси-класса целевой метод не выдал исключение. Таким образом, транзакция совершается нормально.
@Transactional(rollbackFor = Exception.class)
public void transfer(int amount) {
try{
serviceB.sub(amount);
serviceA.add(amount);
} catch (Exception e) {
LOGGER.error("error occur");
}
}
Есть два правильных способа: один — продолжать генерировать исключения в catch, а второй — сообщить Spring, что мою текущую транзакцию нужно откатить.
// 1
throw new RuntimeException(e);
// 2
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
3 Неправильное добавление фасетов
Порядок АОП-аспектов приводит к тому, что транзакция не может быть корректно откатана.Причина в том, что транзакционный аспект имеет самый низкий приоритет, но если пользовательский аспект имеет такой же приоритет, как и он, он все равно является внутренним слоем пользовательского аспекта. В это время, если кастомный аспект не кидает исключение корректно, исключение съедается в catch, то произойдет ситуация, аналогичная второму случаю, прокси-класс не получит информацию об исключении и не откатится.
Решение состоит в том, чтобы также создать исключение в аспекте или установить приоритет пользовательского аспекта на меньшее значение. Но рекомендуется использовать первый метод для выбрасывания исключений в аспекте, но опять же, зачем мы добавляем аспекты в метод, в котором уже есть управление транзакциями... Методы транзакций обычно находятся на уровне Сервиса, и мы можем добавлять аспекты на уровень контроллера.
4 @Transactional транзакции могут быть добавлены только к публичным методам по умолчанию
Непубличные методы сделают транзакцию недействительной. Spring создает прокси для метода и добавляет уведомление о транзакции. Предполагается, что метод является общедоступным. Следует отметить, что либо метод установлен как общедоступный, либо уведомление о транзакции может быть добавлено к непубличным методам.
Рекомендуется использовать общедоступный метод вместо добавления конфигурации
@Bean
public TransactionAttributeSource transactionAttributeSource(){
return new AnnotationTransactionAttributeSource(false);
}
5 Вызов этого метода класса приводит к сбою поведения распространения
Эта проблема возникает при вызове двух методов одного и того же Сервиса.Причина все же в прокси-объекте.Вызов, который мы ожидаем, является вызовом прокси-класса, но если мы вызовем его непосредственно в методе, мы будем вызваны. метод аннулирования транзакции не расширяется с помощью АОП.
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES)
public void a (){
b();
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void b (){
}
План улучшения состоит в том, чтобы позвонить самому себе и сделать себе инъекцию. Возможно, вы видели подобный код для решения этой проблемы. Это решение является наиболее распространенным.
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES)
public void a (){
service.b();
}
Также есть способ получить прокси-объект через AopContext и затем вызвать его. Здесь следует отметить, что использование этого метода требует открытия открытого прокси.
@EnableAspectJAutoProxy(exposeProxy = true)
public class Application {}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public void a (){
((TestService)AopContext.currentProxy()).b();
}
Дело в том, что здесь есть примечание о том, что AopContext не гарантирует работу, э-э-э, ну, давайте внедрять его сами.
/**
* Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal}
* for retrieval via the {@link org.springframework.aop.framework.AopContext} class.
* Off by default, i.e. no guarantees that {@code AopContext} access will work.
* @since 4.3.1
*/
boolean exposeProxy() default false;
Транзакционный не гарантирует атомарного поведения
Эта проблема очень распространена Мы всегда думаем, что добавление управления транзакциями, особенно после добавления Propagation.REQUIRES_NEW, наша транзакция зафиксирует транзакцию после выполнения метода или после добавления ключевого слова synchronized, это атомарная операция, это неправильно ха. Зачем? Начнем с прокси-класса.Прокси-класс запускает транзакцию, выполняет целевой метод и фиксирует транзакцию. Независимо от того, REQUIRES_NEW или ключевое слово synchronized, оно действует только на целевой метод.Даже если целевой метод выполняется успешно, транзакция все равно не зафиксирована.
Для вставки, удаления, обновления, выбора --- для обновления, операторов все атомарно. Но выбрать нет.
Решение состоит в том, чтобы расширить область действия synchronized и заблокировать весь прокси-метод вместо целевого метода. Его также можно контролировать с помощью SQL, чтобы обеспечить атомарность операции, используйте select --- для обновления