Блогът на Анатолий Корсаков

В този пост ще събера най-добрите практики за използване на Spring Transactional.

Сервиз или DAO?

  • Задайте анотацията @Transactional на слоя Service, а не на DAO слоя. Сервизните зърна могат да използват множество DAO, като спазват ACID при една и съща транзакция. В противен случай, ако само DAO дефинира механизъм на транзакция, тогава сервизните компоненти ще увеличат разходите за създаване на множество транзакции за концептуално групирани операции, да не говорим за непостоянното състояние на данните, което рискуваме да получим.

  • В този момент, продължавайки мисълта от предишната, можем да зададем @Transactional (propagation = Propagation.MANDATORY) на ниво клас в нашите DAO, като по този начин принуждаваме потребителите на нашите DAO да инициират управление на транзакции.
  • Трябва да знаете поведението по подразбиране на @Transactional анотацията и да не я използвате сляпо. А именно, освен ако не е изрично посочено, нивото на разпространение ще бъде зададено на Propagation.REQUIRED, което означава да се използва текущата транзакция, в противен случай се създава нова; изолацията ще бъде зададена на стойност Isolation.DEFAULT, тази стойност се определя от основната база данни (по подразбиране за много бази данни тази стойност е Isolation.READ_COMMITED); флагът readOnly е изключен по подразбиране; rollbackFor може да се зададе с клас Throwable, но бъдете внимателни: по подразбиране връщането се извършва само когато е хвърлено RuntimeException (ако този параметър не е зададен).
  • Бъдете внимателни с флага readOnly. Въпреки че @Transactional (readOnly = true, propagation = Propagation.REQUIRED) ще хвърли изключение, когато се опитва да изпълни команда за вмъкване/актуализиране чрез JDBC в транзакция, може да има неочаквано поведение при опит за вмъкване/актуализиране чрез ORM, където операцията ще се извърши крадешком и ще извърши транзакцията успешно ... В ORM среда този флаг трябва да се използва заедно с Propagation.SUPPORTS. В този случай няма да платим разходите за създаване на нова транзакция в проста операция за извличане. Или дори помислете да се отървете от управлението на транзакции за операции SELECT.
  • Бъдете внимателни, когато използвате Propagation.REQUIRES_NEW не на най-високото ниво на транзакции. Това може да доведе до множество проблеми. Всеки път, когато нова основна транзакция се увива около този аспект, в случаите, когато множество транзакции с REQUIRES_NEW са включени в рамките на един метод за транзакционна услуга, могат да възникнат проблеми със загубата на данни. От друга страна, не е забранено използването на анотации на транзакционен метод от най-високо ниво, което всъщност е същото поведение като разпространението по подразбиране.

Spring автоматично връща транзакции за непроверени (Runtime) изключения

Когато възникне изключение по време на изпълнение, Spring маркира текущата транзакция за връщане назад.

Може би най-объркващата част от такива възстановявания е да видим това съобщение „UnexpectedRollbackException: Транзакцията е върната назад, защото е маркирана като само откат“ .

Това е очаквано поведение, но може да обърка, ако не разбирате какво всъщност се е случило. Ако един транзакционен метод извика друг транзакционен метод в друг клас и това вътрешно повикване изведе изключение по време на изпълнение, тогава цялата транзакция ще бъде анулирана. Това ще се случи, освен ако не хванете изключение в извикващия клас. Ако връщането на външния клас не е желателно, можете или да изпълните вътрешната транзакция в нова транзакция (използвайки друго разпространение), или да използвате атрибута noRollBackFor на анотацията @Transactional .

Не използвайте @Transactional анотация върху частни, защитени или методи по подразбиране.

Тази точка идва от първата точка за това как Spring управлява транзакциите. Добавянето на анотация към методи с частни, защитени или модификатори на достъп по подразбиране няма да създаде изключение; обаче, анотацията ще бъде игнорирана

Транзакции в сеч

Много е важно да наблюдавате и разбирате дневниците в случай на непредвидени ситуации. За да активирате регистрационни файлове, добавете към log4j.properties

Това ще ви позволи да проследявате дейности, възникващи в рамките на транзакционни процеси. Дневниците предоставят подробна информация за нови, повторно използвани и активни транзакции, режими на транзакции, транзакции. Това е най-добрият начин да разберете как транзакциите контролират вашето приложение и да разрешите проблема.

Бърза справка за атрибута Транзакционно разпространение

@Transactional (разпространение = размножаване.НЕОБХОДИМО)

Ако не е посочено, стратегията за разпространение по подразбиране е ИЗИСКВАНА.

Други опции са ИЗИСКВАНЕ_NEW, ЗАДЪЛЖИТЕЛНО, ПОДКРЕПИ, NOT_SUPPORTED, НИКОГА и NESTED.

ЗАДЪЛЖИТЕЛНО

  • Показва, че целевият метод не може да се изпълни без активна транзакция. Ако транзакцията вече е стартирала преди извикването на този метод, тогава методите ще бъдат изпълнени в него, или по друг начин, когато методът бъде извикан, ще бъде създадена нова транзакция.

ИЗИСКВА_NEW

  • Означава, че новата транзакция трябва да започне всеки път, когато се извика целевият метод. Ако вече има стартирана транзакция, тя ще бъде спряна, докато започне нова.

ЗАДЪЛЖИТЕЛЕН

  • Показва, че целевият метод изисква активна транзакция за стартиране. Ако не съществува, тогава изпълнението няма да бъде извършено и ще бъде хвърлено изключение.

ПОДДРЪЖКИ

  • Показва, че целевият метод може да бъде изпълнен извън транзакция. Ако има стартирана транзакция, тогава методът ще стартира в нея. Ако няма текуща транзакция, тогава методът ще бъде изпълнен така или иначе, само не в контекст на транзакция.
  • Методите за извадка на данни са най-добрите кандидати за тази опция.

НЕ СЕ ПОДДЪРЖА

  • Показва, че целевият метод не изисква изпълнение на контекст на транзакция. Ако има стартирана транзакция, тя ще бъде спряна.

НИКОГА

  • Означава, че целевият метод ще изведе изключение, ако се изпълни в процес на транзакция.
  • Не препоръчвам да използвате тази опция.

@Transactional (rollbackFor = Exception.class)

  • По подразбиране връщането се случва, когато rollbackFor = RunTimeException.class
  • През пролетта всички класове на API изхвърлят RuntimeException, което означава, че ако някой метод се провали, контейнерът винаги ще върне текущата транзакция.
  • Единственият проблем е с отметнатите изключения. Така че тази опция може да се използва за декларативно връщане на транзакция, ако бъде хвърлено Проверено изключение.

@Transactional (noRollbackFor = IllegalStateException.class)

  • Означава, че връщането не трябва да се случи, ако целевият метод е хвърлил това изключение.