Статья в процессе написания

О чем статья?

  • Разберемся что такое транзакции и зачем они нужны
  • Детально рассмотрим аннотацию @Transactional и её свойства

C чего обычно всё начинается?

Разработчик пишет программу, использует базу, подключает источник данных будь-то REST или Kafka и о многопоточности и конкурентности мало кто думает. Нагрузка на неё возрастает и начинают замечать что данные в некоторых случаях перестают соответстовать действительности и требованиям. С возможными проблемами и путями их решения нам и предстоит ознакомиться.

JTA

Java Transaction API, более известное как JTA, это API для управления транзакциями в Java. Оно поддерживает открытие (start), завершение с сохранением (commit) и откат (rollback) транзакции ресурсонезависимым способом (?) .

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

Транзакция

Транзакция (англ. transaction) - это группа последовательных операций с базой данных, которая представляет собой логическую единицу работы с данными. Транзакция может быть выполнена либо целиком и успешно, соблюдая целостность данных и независимо от параллельно идущих других транзакций, либо не выполнена вообще, и тогда она не должна произвести никакого эффекта. wikipedia.org

Требования к транзакциям

перечислены в наборе ACID. Не буду останавливаться на всех составляющих, разберу лишь I - isolation. Т.к. это требование как раз и относится к нашей теме.

Isolation

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

Уровни изолированности (wiki) и феномены:

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

- Read Uncommited и феномен грязного чтения (Dirty read)

Уровень, имеющий самую плохую согласованность данных, но самую высокую скорость выполнения транзакций. Название уровня говорит само за себя — каждая транзакция видит незафиксированные изменения другой транзакции (феномен грязного чтения) которая впоследствии может не подтвердиться (откатиться).

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

read ucnommited diagram

- Read Commited, неповторяющееся чтение и чтение фантомов

При этом уровне изолированности параллельно выполняемые транзакции видят только зафиксированные изменения других транзакций. Этот уровень обеспечивает защиту от грязного чтения. На этом уровне и более низком выделяют другие проблемы:

  1. неповторяющееся чтение. Ситуация, когда при повторном чтении в рамках одной транзакции, ранее прочитанные данные оказываются изменёнными.
Транзакция 1 Транзакция 2
  SELECT f2 FROM tbl1 WHERE f1=1;
UPDATE tbl1 SET f2=f2+3 WHERE f1=1;  
COMMIT  
  SELECT f2 FROM tbl1 WHERE f1=1;
  1. Чтение фантомов. Ситуация, когда при повторном чтении в рамках одной и транзакции одна и та же выборка даёт разные множества строк.
Транзакция 1 Транзакция 2
  SELECT SUM(f2) FROM tbl1;
INSERT INTO tbl1 (f1,f2) VALUES (15,20);  
COMMIT  
  SELECT SUM(f2) FROM tbl1;

read ucnommited diagram

- Repeatable Read

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

Уровень, позволяет предотвратить феномен неповторяющегося чтения. Т.е. мы не видим в исполняющейся транзакции измененные и удаленные записи другой транзакцией. Но все еще видим вставленные записи из другой транзакции. Чтение фантомов, в теории, никуда не уходит. На практике есть отличия реализации этого уровня блокировки в СУБД. (psql, mysql)

- Serializable

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

Важность транзакций

Как работает аннотация @Transactional?

Spring динамически (если не настроено compile-time weaving) создает Proxy для классов, объявленных аннотацией @Transactional. Он имеет тот же тип что и целевой объект. Он позволяет накручивать дополнительные действия программы “до”, “во время” и “после” вызовов методов обернутого собой объекта. В упрощённом варианте выглядит так:

proxy

Это оборачивание происходит неявно. Другими словами, в промежуток между вызовом и работой метода добавляется дополнительные элементы цепочки в которых и осуществляется работа с транзакционностью. На картинке из документации это можно увидеть в более развёрнутом виде.

spring_transactions

И в статье описано как это всё работает под капотом.

Способы оборачивания

При оборачивание с использованием Spring AOP, прокси могут создаваться через JDK или с CGLIB, а еще возможно, что CTW (compile-time weaving) и LTW (load-time weaving) подключили. Тут есть над чем задуматься. Развернутые ответы нашёл в этой статье.

###

Источники:

Guide to Jakarta EE JTA - baeldung.com

Транзакции в Spring Framework - Kirill Sereda на medium.com

Data Access - Spring Framework Documentation

Spring AOP. Маленький вопросик с собеседования - dmitryk100 на habr.com

@Transactional в Spring под капотом - pyltsinm на habr.com

Уровни изолированности транзакций для самых маленьких на habr.com

Уровень изолированности транзакций - wikipedia.org