Здравствуйте, это AKA Бо Ян, который любит кодирование, хип-хоп и напитки.
Транзакции базы данных являются незаменимым элементом знаний в области разработки серверных приложений. Чтобы лучше поддерживать наши операции с базой данных, Spring поддерживает два метода управления транзакциями в рамках:
- Программные транзакции
- декларативная сделка
В ежедневной разработке бизнеса мы в основном используем декларативные транзакции, т.@Transactional
способ аннотации.
При повседневном использовании Spring может помочь нам очень хорошо реализовать ACID базы данных.(Здесь следует отметить, что Spring выполняет только транзакции программирования, а конечные транзакции данных по-прежнему реализуются базой данных).
但是,只要是人写的代码,就一定会有Bug。
если мы не понимаем@Transactional
Если есть сценарий провала или наступление на яму, то всегда будут какие-то невероятные баги в процессе развития бизнеса.
Это также высокочастотный тестовый сайт во время интервью!
В этой статье будет список@Transactional
сценарии отказов и анализ причин их отказов.
1. Набор сценариев отказа 1: прокси не действует
Весна в моче аналитических нот основана на агенте, если целевой метод не может быть закреплен до пружины, то он не будет весной для управления транзакциями.
Spring может генерировать прокси двумя способами:
- Динамический прокси-сервер JDK на основе интерфейса требует, чтобы целевой класс прокси-сервера должен был реализовать интерфейс, прежде чем его можно будет проксировать.
- Прокси CGLIB на основе реализации подкласса целевого класса
До Spring 2.0, если целевой класс реализует интерфейс, использовался метод динамического прокси JDK, в противном случае прокси генерируется подклассом CGLIB.
А после версии 2.0, если это не указано в конфигурационном файлеspring.aop.proxy-tartget-class
Значение прокси генерируется CGLIB по умолчанию, как показано ниже.
Следуя идее прокси, давайте посмотрим, в каких ситуациях управление транзакциями не будет работать из-за того, что прокси не вступит в силу.
(1) Аннотировать аннотацию к методу интерфейса
@Transactional
Он поддерживает аннотации к методам и классам. После маркировки на интерфейсе, если прокси-метод соответствующего класса реализации интерфейса — CGLIB, прокси целевого класса будет сгенерирован путем создания подклассов, которые не будут преобразованы в@Transactional
, поэтому транзакция не выполняется.
Такого рода ошибки мы до сих пор допускаем относительно редко, в основном мы будем аннотировать аннотацию метода класса реализации интерфейса, что официально не рекомендуется.
(2) Классы или методы, измененные ключевыми словами final и static
CGLIB генерирует прокси-класс, создавая подкласс целевого класса.После модификации с помощью final и static он не может наследовать методы родительского класса и родительского класса.
(3) Внутренний вызов метода класса
Управление транзакциями осуществляется через выполнение прокси.Если метод вызывается внутри, он не будет проходить через логику прокси, поэтому его нельзя будет вызвать.
Например
существуетcreateUserвнутренний метод вызывается вcreateUser1,иcreateUser1Метод устанавливает стратегию распространения транзакций следующим образом:REQUIRES_NEWОднако, поскольку он вызывается напрямую, CreateUser1 не может быть обработан и управление транзакциями не может быть выполнено. После того как метод CreateUser1 выдает аномалию, происходит сбой данных.
Но эта операция в ходе развития нашего бизнеса, казалось бы, довольно распространена, как обеспечить ее успех?
Способ 1: создайте новую службу и перенесите в нее метод, который немного магглов.
Способ 2: внедрить себя в текущий класс и вызвать его через внедренный userService при вызове createUser1
Способ 3: получить прокси-объект через AopContext.currentProxy()
Причина аналогична способу 2, который заключается в доступе к внутренним методам через прокси.
(4) Текущий класс не управляется Spring
Это не значит, что Spring не управляет им как bean-компонентом в контейнере IOC, не говоря уже о проксировании аспектом транзакции.
Такой баг выглядит глупо, но возможно кто-то действительно ошибся.
2. Набор сценариев отказа 2: функции, не поддерживаемые платформой или базовым уровнем
Этот тип сценария сбоя в основном фокусируется на самой структуре при разборе.@Transactional
внутренняя поддержка, когда Если используемый сценарий не поддерживается самим фреймворком, транзакция не вступит в силу.
(1) Непубличные модифицированные методы
мы отмечены@Transactional
Сделайте точку останова на любом методе, и вы увидите точку аспекта транзакции в идее, как показано ниже.
Нажмите, чтобы перейти к этому методу, в начале есть такой вызов
продолжай входить
Вы можете увидеть это предложение
Непубличные модифицированные методы не поддерживаются для управления транзакциями.
(2) Многопоточный вызов
С помощью той же операции, что и выше, мы можем вводить слой за слоем вTransactionAspectSupport.prepareTransactionInfo
метод.
Обратите внимание на следующее предложение
Отсюда мы знаем, что информация о транзакции привязана к потоку.
Следовательно, в многопоточной среде информация о транзакциях независима, что приведет к различиям в захвате транзакций 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 механизмов распространения, а именно:
Вышеупомянутые механизмы распространения, которые не поддерживают транзакции: PROPAGATION_SUPPORTS, PROPAGATION_NOT_SUPPORTED, PROPAGATION_NEVER.
Если настроены эти три метода распространения, транзакция не будет откатываться при возникновении исключения.
(2) Свойство rollbackFor задано неправильно
По умолчанию транзакции откатывают только исключения времени выполнения и ошибки, а не проверенные исключения (такие как IOException).
Поэтому, если в методе возникает исключение ввода-вывода, транзакция по умолчанию завершается ошибкой.
Мы можем указать@Transactional(rollbackFor = Exception.class)
способ поймать все исключения.
(3) Исключение перехватывается внутри
UserService
UserService1
Приведенный выше код 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.
4. Резюме
В этой статье анализ для всех@Transactional
12 сценариев, в которых аннотации не работают во время использования
Наконец,@Transactional
Несмотря на то, что аннотации ароматны, под сложной бизнес-логикой, чтобы лучше управлять транзакциями и контролировать детализацию транзакций во время бизнес-обработки, я все же рекомендую вам использовать программные транзакции.
5. Свяжитесь со мной
Если есть какие-то неточности в тексте, поправьте меня, текст писать непросто, ставьте лайк, хорошо~
WeChat: baiyan_lou
Общественный номер: дядя Бай Ян