Завершить 12 неудачных сценариев @Transactional за один раз.

Spring Boot задняя часть
Завершить 12 неудачных сценариев @Transactional за один раз.

Здравствуйте, это AKA Бо Ян, который любит кодирование, хип-хоп и напитки.

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

  • Программные транзакции
  • декларативная сделка

В ежедневной разработке бизнеса мы в основном используем декларативные транзакции, т.@Transactionalспособ аннотации.

При повседневном использовании Spring может помочь нам очень хорошо реализовать ACID базы данных.(Здесь следует отметить, что Spring выполняет только транзакции программирования, а конечные транзакции данных по-прежнему реализуются базой данных).

但是,只要是人写的代码,就一定会有Bug。

если мы не понимаем@TransactionalЕсли есть сценарий провала или наступление на яму, то всегда будут какие-то невероятные баги в процессе развития бизнеса.

Это также высокочастотный тестовый сайт во время интервью!

В этой статье будет список@Transactionalсценарии отказов и анализ причин их отказов.

1. Набор сценариев отказа 1: прокси не действует

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

Spring может генерировать прокси двумя способами:

  1. Динамический прокси-сервер JDK на основе интерфейса требует, чтобы целевой класс прокси-сервера должен был реализовать интерфейс, прежде чем его можно будет проксировать.
  2. Прокси CGLIB на основе реализации подкласса целевого класса

До Spring 2.0, если целевой класс реализует интерфейс, использовался метод динамического прокси JDK, в противном случае прокси генерируется подклассом CGLIB.

А после версии 2.0, если это не указано в конфигурационном файлеspring.aop.proxy-tartget-classЗначение прокси генерируется CGLIB по умолчанию, как показано ниже.

image-20211230104332833.png

Следуя идее прокси, давайте посмотрим, в каких ситуациях управление транзакциями не будет работать из-за того, что прокси не вступит в силу.

(1) Аннотировать аннотацию к методу интерфейса

@TransactionalОн поддерживает аннотации к методам и классам. После маркировки на интерфейсе, если прокси-метод соответствующего класса реализации интерфейса — CGLIB, прокси целевого класса будет сгенерирован путем создания подклассов, которые не будут преобразованы в@Transactional, поэтому транзакция не выполняется.

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

(2) Классы или методы, измененные ключевыми словами final и static

CGLIB генерирует прокси-класс, создавая подкласс целевого класса.После модификации с помощью final и static он не может наследовать методы родительского класса и родительского класса.

(3) Внутренний вызов метода класса

Управление транзакциями осуществляется через выполнение прокси.Если метод вызывается внутри, он не будет проходить через логику прокси, поэтому его нельзя будет вызвать.

Например

image-20211230143307601.png

существуетcreateUserвнутренний метод вызывается вcreateUser1createUser1Метод устанавливает стратегию распространения транзакций следующим образом:REQUIRES_NEWОднако, поскольку он вызывается напрямую, CreateUser1 не может быть обработан и управление транзакциями не может быть выполнено. После того как метод CreateUser1 выдает аномалию, происходит сбой данных.

Но эта операция в ходе развития нашего бизнеса, казалось бы, довольно распространена, как обеспечить ее успех?

Способ 1: создайте новую службу и перенесите в нее метод, который немного магглов.

Способ 2: внедрить себя в текущий класс и вызвать его через внедренный userService при вызове createUser1

image-20211230143808766.png

Способ 3: получить прокси-объект через AopContext.currentProxy()

image-20211230143957694.png

Причина аналогична способу 2, который заключается в доступе к внутренним методам через прокси.

(4) Текущий класс не управляется Spring

Это не значит, что Spring не управляет им как bean-компонентом в контейнере IOC, не говоря уже о проксировании аспектом транзакции.

Такой баг выглядит глупо, но возможно кто-то действительно ошибся.

2. Набор сценариев отказа 2: функции, не поддерживаемые платформой или базовым уровнем

Этот тип сценария сбоя в основном фокусируется на самой структуре при разборе.@Transactionalвнутренняя поддержка, когда Если используемый сценарий не поддерживается самим фреймворком, транзакция не вступит в силу.

(1) Непубличные модифицированные методы

мы отмечены@TransactionalСделайте точку останова на любом методе, и вы увидите точку аспекта транзакции в идее, как показано ниже.

image-20211230135414452.png

Нажмите, чтобы перейти к этому методу, в начале есть такой вызов

image-20211230140357800.png

продолжай входить

image-20211230140438344.png

Вы можете увидеть это предложение

image-20211230140504211.png

Непубличные модифицированные методы не поддерживаются для управления транзакциями.

(2) Многопоточный вызов

С помощью той же операции, что и выше, мы можем вводить слой за слоем вTransactionAspectSupport.prepareTransactionInfoметод.

Обратите внимание на следующее предложение

image-20211230161110957.png

Отсюда мы знаем, что информация о транзакции привязана к потоку.

Следовательно, в многопоточной среде информация о транзакциях независима, что приведет к различиям в захвате транзакций Spring.

Мы должны обратить особое внимание на эту сцену!

дать вам пример

Основной поток A вызывает поток B для сохранения данных с идентификатором 1, а затем основной поток A ждет, пока поток B завершит выполнение, а затем запрашивает данные с идентификатором 1 через поток A.

В это время вы обнаружите, что данные с идентификатором 1 не могут быть запрошены в основном потоке A. Поскольку два потока находятся в разных транзакциях Spring, это фактически заставляет их существовать в разных транзакциях в Mysql.

В Mysql MVCC гарантирует, что при чтении моментального снимка поток считывает только данные, меньшие, чем текущий номер транзакции.В потоке B номер транзакции явно больше, чем у потока A, поэтому никакие данные не могут быть запрошены.

(3) Сама база данных не поддерживает транзакции

Например, механизм хранения Myisam в Mysql не поддерживает транзакции, его поддерживает только механизм хранения innodb.

Вероятность этой проблемы крайне мала, поскольку Mysql5 по умолчанию использует механизм хранения innodb.

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

(4) Транзакция не открыта

Это больше проблем с маглом в проекте Springboot больше не существует, имеетDataSourceTransactionManagerAutoConfigurationУправление транзакциями включено по умолчанию.

Однако в проекте MVC вам также необходимо вручную настроить параметры, связанные с транзакцией, в файле applicationContext.xml. Если вы забудете настроить, транзакция точно не вступит в силу.

3. Сценарий отказа. Эпизод 3: Неправильное использование @Transactional

Обратите внимание, обратите внимание, все это все ошибки, которые появятся на высокой частоте!

(1) механизм распространения ошибок

Spring поддерживает 7 механизмов распространения, а именно:

image.png

Вышеупомянутые механизмы распространения, которые не поддерживают транзакции: PROPAGATION_SUPPORTS, PROPAGATION_NOT_SUPPORTED, PROPAGATION_NEVER.

Если настроены эти три метода распространения, транзакция не будет откатываться при возникновении исключения.

(2) Свойство rollbackFor задано неправильно

image-20211230165715830.png

По умолчанию транзакции откатывают только исключения времени выполнения и ошибки, а не проверенные исключения (такие как IOException).

Поэтому, если в методе возникает исключение ввода-вывода, транзакция по умолчанию завершается ошибкой.

Мы можем указать@Transactional(rollbackFor = Exception.class)способ поймать все исключения.

(3) Исключение перехватывается внутри

UserService

image-20211230171002480.png

UserService1

image-20211230171013646.png

Приведенный выше код UserService вызывает метод в UserService1 и перехватывает исключение, созданное в UserService1.

В консоли вы увидите такую ​​ошибку:

 org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

Маркировка по умолчанию@TransactionalМеханизм распространения транзакций аннотированного метода:REQUIRED, его особенность заключается в поддержке текущей транзакции, то есть в присоединении к текущей транзакции. Мы запускаем транзакцию в UserService, а затем выбрасываем исключение в UserService1, чтобы откатить транзакцию в UserService, пометив ее как доступную только для чтения.

Но в UserService ловим исключение, в это время транзакция на UserService считает транзакцию зафиксированной нормально. Наконец, когда обнаруживается, что транзакция доступна только для чтения и для нее был выполнен откат, выдается указанное выше исключение.

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

(4) Вложенные транзакции

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

Есть два способа реализовать описанную выше логику:

1. Весь метод непосредственно в UserService1 обернут с помощью try/catch

2. Используйте механизм распространения Propagation.REQUIRES_NEW в UserService1.

image-20211230173219220.png

4. Резюме

В этой статье анализ для всех@Transactional12 сценариев, в которых аннотации не работают во время использования

image-20211230224957141.png

Наконец,@TransactionalНесмотря на то, что аннотации ароматны, под сложной бизнес-логикой, чтобы лучше управлять транзакциями и контролировать детализацию транзакций во время бизнес-обработки, я все же рекомендую вам использовать программные транзакции.

5. Свяжитесь со мной

Если есть какие-то неточности в тексте, поправьте меня, текст писать непросто, ставьте лайк, хорошо~

WeChat: baiyan_lou

Общественный номер: дядя Бай Ян