Для чего нужны транзакции в базе данных?
Использую надстройку для ADO linq2db. Там есть методы, BeginTransaction, RollbackTransaction, CommitTransaction. В моём приложении использую стандартные методы для работы с бд, такие как Insert, Update, Delete, Select. Пытался искать по документации, но ничего толкого, а тем более с примерами найти не смог. Про сами транзакции пишут, что они повышают производительность и т д. Но как их использовать допустим для добавления записи в бд не знаю.
Отслеживать
28.9k 5 5 золотых знаков 28 28 серебряных знаков 55 55 бронзовых знаков
задан 16 апр 2021 в 21:35
121 9 9 бронзовых знаков
Если я правильно понимаю, транзакции нужны для логической группировки (unit of work) и изоляции: чтобы можно было отменить начатую операцию, даже если половину данных уже как бы изменена, и чтобы пока изменения не завершатся, другие не видели «наполовину изменённые» данные
16 апр 2021 в 21:41
непонятно, что вы искали и что не нашли. Ваш вопрос по транзацияи в общем или по вашей БД в частности?
16 апр 2021 в 21:42
что гугл про транзации гоаорит, читали? Для чего нужны транзакции в базе данных
16 апр 2021 в 21:43
Транза́кция (англ. transaction) — группа последовательных операций с базой данных, которая представляет собой логическую единицу работы с данными. Транзакция может быть выполнена либо целиком и успешно, соблюдая целостность данных и независимо от параллельно идущих других транзакций, либо не выполнена вообще, и тогда она не должна произвести никакого эффекта.
16 апр 2021 в 21:43
Транзакции в linq2db делают то же самое, что в любом другом способе работы с БД. будь то EF, Dapper, чистый ADO.NET.
16 апр 2021 в 22:58
1 ответ 1
Сортировка: Сброс на вариант по умолчанию
Повышение производительности, это одно и далеко не самое главное, применение транзакций.
Обычно, когда говорят о использовании транзакций для производительности, то имеется ввиду, что можно сэкономить на накладных расходах делая больше операций в одной транзакции. При выполнении транзакции всегда есть накладные расходы и дорогостоящие операции. Самое главное, что фиксация транзакции (commit), всегда требует записи на диск и ожидания, что запись произошла. Даже если вы выполняете простую операцию, типа Update и явно не управляете транзакцией, она все равно будет создана не явно и вы несете эти накладный расходы.
Выполнение нескольких операций в одной транзакции может существенно повысить производительность.
Но как я уже написал, это далеко не самое главное. Более важно это поддержка атомарности, изоляции и целостности. Приведу несколько простых типичных примеров, когда использование транзакций существенно упрощает жизнь.
Пример 1
Типичный пример, когда нужно сделать два модифицирующих запроса. И важно, чтоб они оба были выполнены (или ни один не был). Перевод денег с одного счета на другой.
Операция 1: уменьшить значение счета A на 100 USD. Операция 2: увеличить значение счета B на 100 USD.
Допустим не используются транзакции. Если произойдет проблема после того, как выполнилась первая операция, но не выполнилась вторая, то у нас пропало 100 USD. И очень нетривиально такую проблему разрулить.
Варианты типа, просто вернуть эти 100 USD на счет А сталкиваются с проблемой, что очень непросто сделать так, чтоб информация о том, что нужно сделать этот возврат не потерялась, т.к. проблема из-за которой это случилось может быть в том, что сервер на котором выполнялся этот код просто вышел из строя (сгорел, например) и у нас нет информации о том, что он успел сделать в рамках этой транзакции. Логи тоже могли пропасть и мы даже не будем знать, что у нас выполнялись какие-то операции, когда сервер вышел из строя.
Так же может быть проблема на стороне самой БД, например, БД недоступна вообще по сети — сгорела сетевая карта на сервере БД или закончилось место на диске, где БД хранит данные. И вернуть деньги назад, что означает сделать запрос в БД, который добавит к A 100 USD, просто невозможно.
И что если во время выполнения кода компенсирующего операцию 1 произойдет сбой? Надеюсь идея понятна: в абсолютно любом месте программы может произойти программный или аппаратный сбой. При этом возникает куча нетривиальных проблем.
Использование же транзакции тут простой способ полностью избежать данной проблемы. Транзакция (т.е. эти две операции) либо полностью пройдет успешно, либо (если сервер упадет после первой операции) вообще не пройдет. Во втором случае, БД автоматически отменит первую операцию и эти 100 USD никуда не пропадут.
Пример 2
Программа может генерировать отчет и для этого делать несколько запросов в БД. Например, на одной странице excel файла показан баланс по текущим счетам пользователей, а на другой баланс по депозитным счетам тех же пользователей. И представим, что это делается двумя запросами (это не невероятное предположение, обычно запросы такого типа довольно сложные, в них куча логики и делать это одним большим запросом часто сложно с точки зрения как производительности так и поддержки кода этого запроса).
На большой базе каждый такой запрос может занимать продолжительное время. Теперь, если мы не используем транзакции, то есть проблема с тем, что каждый из запросов видит состояние базы данных в разные моменты времени. И например может быть так, что между этими двумя запросами могут вклиниться другие операции, которые выполняются с этими же счетами. Например, какой-то клиент, может открыть новый депозитный счет и перевести деньги на него с текущего счета.
Если последовательность действий была такая:
Время | Операции генерации отчета | Операция открытия депозита ----------+------------------------------+----------------------------------- t1 | получить часть отчета | | для текущих счетов запросом | | в БД | ----------+------------------------------+----------------------------------- t2 | | перевести 100 USD с текущего счета | | клиента A на депозитный ----------+------------------------------+----------------------------------- t3 | получить часть отчета | | для депозитных счетов | | запросом в БД |
Получится, что одни и те же 100 USD будут два раза включены в отчет для клиента A и будет выглядеть, что у него 200 USD, хотя на самом деле только 100.
Используя транзакцию при генерации отчета можно сделать так, чтоб все запросы видели состояние БД на определенный момент.
Что такое транзакция
Транзакция — это набор операций по работе с базой данных (БД), объединенных в одну атомарную пачку.
Транзакционные базы данных (базы, работающие через транзакции) выполняют требования ACID, которые обеспечивают безопасность данных. В том числе финансовых данных =) Поэтому разработчики их и выбирают.
Я расскажу о том, что такое транзакция. Как ее открыть, и как закрыть. И почему это важно — закрывать транзакцию. И тогда при написании запросов к базе у вас будет осознанное понимание, что происходит там, под капотом, и зачем же нужен этот обязательный коммит после апдейта.
Что такое транзакция
Транзакция — это архив для запросов к базе. Он защищает ваши данные благодаря принципу «всё, или ничего».
Представьте, что вы решили послать другу 10 файликов в мессенджере. Какие есть варианты:
- Кинуть каждый файлик отдельно.
- Сложить их в архив и отправить архив.
Вроде бы разницы особой нет. Но что, если что-то пойдет не так? Соединение оборвется на середине, сервер уйдет в ребут или просто выдаст ошибку.
В первом случае ваш друг получит 9 файлов, но не получит один.
Во втором не получит ничего. Нет промежуточных состояний. Или получил всё, или не получил ничего. Но зато если произошла ошибка, вы снова перешлете сообщение. И друг получит все файлики разом, не придется проверять «не потерялся ли кто».
Казалось бы, ну недополучил файлик, что с того? А если это критично? Если это важные файлики? Например, для бухгалтерии. Потерял один файлик? Значит, допустил ошибку в отчете для налоговой. Значит, огребешь штраф и большие проблемы! Нет, спасибо, лучше файлы не терять!
И получается, что тебе надо уточнять у отправителя:
— Ты мне сколько файлов посылал?
— 10
— Да? У меня только 9. Давай искать, какой продолбался.
И сидите, сравниваете по названиям. А если файликов 100 и потеряно 2 штуки? А названия у них вовсе не «Отчет 1», «Отчет 2» и так далее, а «hfdslafebx63542437457822nfhgeopjgrev0000444666589.xml» и подобные. Уж лучше использовать архив! Тогда ты или точно всё получил, или не получил ничего и делаешь повторную попытку отправки.
Так вот! Транзакция — это тот же архив для запросов. Принцип «всё, или ничего». Или выполнены все запросы, которые разработчик упаковал в одну транзакцию, или ни один.
Допустим, вы переводите все деньги с одной карточки на другую. Выглядит это «внутри» системы как несколько операций:
delete from счет1 where счет = счет 1
insert into счет2 values (‘сумма’)
Принцип «всё или ничего» тут очень помогает. Было бы обидно, если бы деньги со счета1 списались, но на счет2 не поступили. Потому что соединение оборвалось или вы в номере счета опечатались и система выдала ошибку.
Но благодаря объединению запросов в транзакцию при возникновении ошибки зачисления мы откатываем и операцию списания. Деньги снова вернулись на счет 1!
Если говорить по-научному, то транзакция — упорядоченное множество операций, переводящих базу данных из одного согласованного состояния в другое. Согласованное состояние — это состояние, которое подходит под бизнес-логику системы. То есть у нас не остается отрицательный баланс после перевода денег, номер счета не «зависает в воздухе», не привязанный к человеку, и тому подобное.
Как отправить транзакцию
Чтобы обратиться к базе данных, сначала надо открыть соединение с ней. Это называется коннект (от англ. connection, соединение). Коннект — это просто труба, по которой мы посылаем запросы.
Чтобы сгруппировать запросы в одну атомарную пачку, используем транзакцию. Транзакцию надо:
- Открыть.
- Выполнить все операции внутри.
- Закрыть.
Как только мы закрыли транзакцию, труба освободилась. И ее можно переиспользовать, отправив следующую транзакцию.
Можно, конечно, каждый раз закрывать соединение с БД. И на каждое действие открывать новое. Но эффективнее переиспользовать текущие. Потому что создание нового коннекта — тяжелая операция, долгая.
При настройке приложения администратор указывает, сколько максимально открытых соединений с базой может быть в один момент времени. Это называется пул соединений — количество свободных труб.
Разработчик берет соединение из пула и отправляет по нему транзакцию. Как только транзакция закрывается (неважно, успешно она прошла или откатилась), соединение возвращается в пул, и его может использовать следующая бизнес-операция.
Как открыть транзакцию
Зависит от базы данных. В Oracle транзакция открывается сама, по факту первой изменяющей операции. А в MySql надо явно писать «start transaction».
Как закрыть транзакцию
Тут есть 2 варианта:
- COMMIT — подтверждаем все внесенные изменения;
- ROLLBACK — откатываем их;
И вся фишка транзакционной базы в том, что база сначала применяет запрос «виртуально», реально ничего в базе не изменив. Ты можешь посмотреть, как запрос изменит базу, ничего при этом не сохраняя.
Например, я пишу запрос:
insert into clients (name, surname) values ('Иван', 'Иванов'); -- добавь в таблицу клиентов запись с именем «Иван» и фамилиев «Иванов»
Запрос выполнен успешно, хорошо! Теперь, если я сделаю select из этой таблицы, прям тут же, под своим запросом — он находит Иванова! Я могу увидеть результат своего запроса.
Но! Если открыть графический интерфейс программы, никакого Иванова мы там не найдем. И даже если мы откроем новую вкладку в sql developer (или в другой программе, через которую вы подключаетесь к базе) и повторим там свой select — Иванова не будет.
А все потому, что я не сделала коммит, не применила изменения:
insert into clients (name, surname) values ('Иван', 'Иванов'); commit;
Я могу добавить кучу данных. Удалить полтаблицы. Изменить миллион строк. Но если я закрою вкладку sql developer, не сделав коммит, все эти изменения потеряются.
Когда я впервые столкнулась с базой на работе, я часто допускала такую ошибку: подправлю данные «на лету» для проведения теста, а в системе ничего не меняется! Почему? Потому что коммит сделать забыла.
На самом деле это удобно. Ведь если ты выполняешь сложную операцию, можно посмотреть на результат. Например, удаляем тестовые данные. Написали кучу условий из серии:
Удалили. Делаем select count — посмотреть количество записей в таблице. А там вместо миллиона строк осталось 100 тысяч! Если база реальная, то это очень подозрительно. Врядли там было СТОЛЬКО тестовых записей.
Проверяем свой запрос, а мы там где-то ошиблись! Вместо «И» написали «ИЛИ», или как-то еще. Упс. Хорошо еще изменения применить не успели. Вместо коммита делаем rollback.
Тут может возникнуть вопрос — а зачем вообще нужен ROLLBACK? Ведь без коммита ничего не сохранится. Можно просто не делать его, и всё. Но тогда транзакция будет висеть в непонятном статусе. Потому что ее просто так никто кроме тебя не откатит.
Или другой вариант. Нафигачили изменений:
Но видим, что операцию надо отменять. Проверочный select заметил, что база стала неконсистентной. А мы решили «Ай, да ладно, коммит то не сделали? Значит, оно и не сохранится». И вернули соединение в пул.
Следующая операция бизнес-логики берет это самое соединение и продолжает в нем работать. А потом делает коммит. Этот коммит относился к тем 3 операциям, что были внутри текущей транзакции. Но мы закоммитили еще и 10 других — тех, что в прошлый раз откатить поленились. Тех, которые делают базу неконсистентной.
Так что лучше сразу сделайте откат. Здоровей система будет!
Итого
Транзакция — набор операций по работе с базой данных, объединенных в одну атомарную пачку.
Одной операции всегда соответствует одна транзакция, но в рамках одной транзакции можно совершить несколько операций (например, несколько разных insert можно сделать, или изменить и удалить данные. ).
Чтобы отправить транзакцию к базе, нам нужно создать соединение с ней. Или переиспользовать уже существующее. Соединение называют также коннект (англ connection) — это просто труба, по которой отправляются запросы. У базы есть пул соединений — место, откуда можно взять любое и использовать, они там все свободные.
В некоторых системах транзакцию нужно открыть, в других она открывается сама. А вот закрыть ее нужно самостоятельно. Варианты:
- COMMIT — подтверждаем все внесенные изменения;
- ROLLBACK — откатываем их;
Делая комит, мы заканчиваем одну бизнес-операцию, и возвращаем коннект в пул без открытой транзакции. То есть просто освобождаем трубу для других. Следующая бизнес-операция берет эту трубу и фигачит в нее свои операции. Поэтому важно сделать rollback, если изменения сохранять не надо. Не откатите и вернете соединение в пул? Его возьмет кто-то другой и сделает коммит. Своих изменений, и ваших, неоткаченных.
Не путайте соединение с базой (коннект) и саму транзакцию. Коннект — это просто труба, операции (update, delete…) мы посылаем по трубе, старт транзакции и commit /rollback — это группировка операций в одну атомарную пачку.
См также:
Блокировки транзакций — что может пойти не так при одновременном редактировании
Основы транзакций в Spring и JDBC
В этой статье мы разберемся, что такое транзакции. Какими обладают транзакции — ACID. Как транзакции выполняются на уровне JDBС, а также на уровне Spring.
12 окт. 2022 · 7 минуты на чтение
Мы начнём изучение транзакций с нуля, шаг за шагом погружаясь в тему. Быстро рассмотрим проблему, которую решают транзакции. Далее посмотрим, как написать транзакцию на SQL. А потом разберёмся с управлением транзакциями в JDBC. Всё, что делает Spring, основано на JDBC. И вы сэкономите кучу времени при работе с аннотацией @Transactional , если усвоите эти основы.
Спонсор поста
Переведенная статья
Данная статья является переводом и адаптацией англоязычной статьи. Я тщательно перевожу статью на русский, актуализирую устаревшие моменты, а также проверяю или пишу примеры кода, которые вы можете запустить самостоятельно.
– – – –
Spring Transaction Management: @Transactional In-Depth
Проблематика
Что такое транзакция и зачем она нужна проще объяснить на примере. Допустим, Вася переводит Пете 100 рублей. Для выполнения этой бизнес-функции нам потребуется три действия:
- Списать деньги с баланса Васи;
- Записать операцию перевода от Васи к Пете;
- Добавить денег на баланс Пете;
Представим, что мы списали деньги с баланса Васи, а потом произошла ошибка. В итоге на балансе у Васи нет денег, а у Пети они не появились. Приятная ситуация для банка, но неприятная для Васи и Пети.
Для решения таких проблем и существуют транзакции. Транзакция позволит нам объединить эти операции таким образом, что по итогу мы либо запишем все изменения, либо ничего. То есть объединить несколько различных действий в атомарную операцию.
Свойства транзакций ACID
Транзанкции обладют свойствами, которые называют ACID:
Атомарность (Atomicity) гарантирует, что никакая транзакция не будет зафиксирована в системе частично. Будут либо выполнены все операции, либо ни одной.
Согласованность (Consistency). Выполненая транзакция, сохраняет согласованность базы данных. Согласованность является более широким понятием, чем может показаться.
Например, в банковской системе может существовать требование равенства суммы, списываемой с одного счёта, сумме, зачисляемой на другой. Это бизнес-правило и оно не может быть гарантировано только проверками целостности базы данных. Это поведение должны учитывать программисты при написании кода транзакций. Если какая-либо транзакция произведёт списание, но не произведёт зачисления, то система останется в некорректном состоянии и свойство согласованности будет нарушено.
Изолированность (Isolation). Во время выполнения транзакции параллельные транзакции не должны оказывать влияние на её результат.
Изолированность обходится дорого, поэтому в реальных базах данных существуют режимы, не полностью изолирующие транзакцию. Об уровнях изоляции мы поговорим в отдельной статье.
Устойчивость (Durability). Независимо от проблем с оборудованием, изменения, сделанные успешно завершённой транзакцией, должны остаться сохранёнными после возвращения системы в работу.
Транзакции в SQL
Коротко рассмотрим, как сделать транзакцию в SQL. Для этого используют ключевые слова BEGIN , COMMIT , ROLLBACK .
Возвращаясь к примеру с переводами, представим, что у нас есть 2 таблицы:
И мы хотим сделать перевод между первым и вторым пользователем 2. Других пользователей у нас в системе нет.
UPDATE person SET balance = (balance - 10) WHERE INTO transaction(person_from, person_to, amount) values (1, 3, 10); UPDATE person SET balance = (balance + 10) WHERE >Мы уменьшаем баланс первого пользователя, потом создаём запись в таблице переводов, но случайно ошибаемся и записываем перевод несуществующему третьему пользователю. После чего пытаемся обновить баланс второго пользователя.
Между таблицами transaction и person есть связь, а пользователя с идентификатором 3 не существует, поэтому мы не сможем выполнить INSERT . И дальнейшее выполнение кода будет остановлено, то есть у первого пользователя баланс уменьшается, а у второго не увеличивается.
Исправим эту ситуацию с помощью транзакции:
BEGIN; UPDATE person SET balance = (balance - 10) WHERE INTO transaction(person_from, person_to, amount) values (1, 3, 10); UPDATE person SET balance = (balance + 10) WHERE >Мы обернули код в команды BEGIN и COMMIT . Но теперь, когда произойдёт ошибка в операторе INSERT , изменения, внесенные первым оператором UPDATE , не будут сохранены в базу данных. Таким образом, все три изменения будут записаны либо вместе, либо не будут записаны вовсе. А теперь переходим к родному для нас JDBC.
Учтите, что после ошибки в транзакции необходимо вызвать оператор ROLLBACK . Для упрощения в примере этот момент опущен.
Управление транзакциями в JDBC
Первое, что вам стоит понять и запомнить: не имеет значения, используете ли вы аннотацию @Transactional от Spring, Hibernate, jOOQ или любую другую библиотеку для работы с базой данных. В конечном счёте все они делают одно и то же — открывают и закрывают транзакции базы данных.
Обычный код управления транзакциями JDBC выглядит следующим образом:
import java.sql.Connection; Connection connection = dataSource.getConnection(); // (1) try (connection) < connection.setAutoCommit(false); // (2) // execute some SQL statements. connection.commit(); // (3) >catch (SQLException e) < connection.rollback(); // (4) >
- Для запуска транзакций вам необходимо подключение к базе данных. DriverManager.getConnection(url, user, password) тоже подойдёт, хотя в большинстве корпоративных приложений вы будете иметь настроенный источник данных и получать соединения из этого источника.
- Это единственный способ начать транзакцию базы данных в Java, возможно, звучит немного странно. Вызов setAutoCommit(true) гарантирует, что каждый SQL-оператор будет автоматически завёрнут в собственную транзакцию, а setAutoCommit(false) — наоборот. Теперь вы должны управлять жизнью транзакции. Обратите внимание, что флаг autoCommit действует в течение всего времени, пока соединение открыто.
- Фиксируем транзакцию
- Или откатываем изменения, если возникло исключение.
Управление транзакциями в Spring
Поскольку теперь у вас есть понимание транзакций JDBC, посмотрим, как Spring управляет транзакциями. Это также применимо и к Spring Boot и Spring MVC.
В обычном JDBC у вас есть один способ ( setAutocommit(false) ) управлять транзакциями, Spring предлагает вам множество различных, более удобных способов добиться того же самого.
Программное управление транзакциями Spring
Первый, но довольно редко используемый способ внедрения транзакций – программный. Либо через TransactionTemplate , либо через PlatformTransactionManager . Это выглядит следующим образом:
@Service public class UserService < @Autowired private TransactionTemplate template; public Long registerUser(User user) < Long ->< // execute some SQL that e.g. // inserts the user into the db and returns the autogenerated id return id; >); > >
По сравнению с обычным примером JDBC:
- Вам не нужно открывать и закрывать соединений с базой данных самостоятельно. Вместо этого, вы используете Transaction Callbacks.
- Также не нужно ловить SQLExceptions , так как Spring преобразует их в исключения времени выполнения.
- Кроме того, вы лучше интегрируетесь в экосистему Spring. TransactionTemplate будет использовать TransactionManager внутри, который будет использовать источник данных. Всё это бины, которые выукажете в конфигурации, но о которых вам больше не придется беспокоиться в дальнейшем.
Хотя это, и выглядит небольшим улучшением по сравнению с JDBC, программное управление транзакциями — это не самый лучший способ. Вместо этого, используйте декларативное управление транзакциями.
Декларативное управление транзанкциями
Посмотрим, как обычно выглядит управление транзакциями в Spring:
public class UserService < @Transactional public Long registerUser(User user) < // execute some SQL that e.g. // inserts the user into the db and retrieves the autogenerated id // userDao.save(user); return id; >>
Никакого лишнего «технического» кода. Вместо этого, нужно сделать две вещи:
- Убедиться, что одна из Spring конфигурации аннотирована @EnableTransactionManagement . В SpringBoot даже этого делать не нужно.
- Убедиться, что вы указали менеджер транзакций в конфигурации. Это делается в любом случае.
И тогда Spring будет обрабатывать транзакции за вас. Любой публичный метод бина, который вы аннотируете @Transactional , будет выполняться внутри транзакции базы данных.
Итак, чтобы аннотация @Transactional заработала, делаем следующее:
@Configuration @EnableTransactionManagement public class MySpringConfig < @Bean public PlatformTransactionManager txManager() < return yourTxManager; // more on that later >>
Вооружившись знаниями из примера про транзакций JDBC, приведенный выше код можно упрощенно представить следующим образом:
Как видно из этой схемы, у прокси следующие задачи:
- открытие и закрытие соединений/транзакций с базой данных;
- А затем делегирование выполнения настоящему UserService , тому, который вы написали;
- А другие бины, такие как ваш UserRestController , никогда не узнают, что они работают с прокси, а не с настоящим сервисом;
Для чего нужен менеджер транзакций?
Теперь не хватает только одной важной части информации, хотя мы уже несколько раз упоминали о ней.
Ваш UserService проксируется во время выполнения, и прокси управляет транзакциями. Но не сам прокси управляет всем транзакционным состоянием, прокси делегирует работу менеджеру транзакций.
Spring предлагает вам интерфейс PlatformTransactionManager / TransactionManager , который по умолчанию поставляется с парой удобных реализаций. Одна из них – DataSourceTransactionManager .
Он делает то же самое, что вы делали до сих пор для управления транзакциями, но сначала рассмотрим необходимую конфигурацию Spring:
@Bean public DataSource dataSource() < return new MysqlDataSource(); // (1) >@Bean public PlatformTransactionManager txManager() < return new DataSourceTransactionManager(dataSource()); // (2) >
- Создаем источник базы данных. В этом примере используется MySQL.
- Создаем менеджер транзакций, которому нужен источник данных, чтобы иметь возможность управлять транзакциями.
Все менеджеры транзакций имеют такие методы, как doBegin() или doCommit() , которые выглядят следующим образом:
public class DataSourceTransactionManager implements PlatformTransactionManager < @Override protected void doBegin(Object transaction, TransactionDefinition definition) < Connection newCon = obtainDataSource().getConnection(); // . con.setAutoCommit(false); // yes, that's it! >@Override protected void doCommit(DefaultTransactionStatus status) < // . Connection connection = status.getTransaction().getConnectionHolder().getConnection(); try < con.commit(); >catch (SQLException ex) < throw new TransactionSystemException("Could not commit JDBC transaction", ex); >> >
Таким образом, менеджер транзакций источника данных использует при управлении транзакциями такой же код, который вы видели в разделе про JDBC.
Учитывая это, улучшим схему:
- Если Spring обнаруживает аннотацию @Transactional на бине, он создаёт динамический прокси этого бина.
- Прокси имеет доступ к менеджеру транзакций и будет просить его открывать и закрывать транзакции/соединения.
- Сам менеджер транзакций будет просто управлять старым добрым соединением JDBC.
Резюмирую
К этому моменту вы должны иметь довольно хорошее представление о том, что такое транзакция. Как она выглядит в SQL. И как управление транзакциями работает с фреймворком Spring.
Главным выводом должно быть то, что в итоге не имеет значения, какой фреймворк вы используете, всё дело в основах JDBC.
Транзакции не всегда изолированы друг от друга, поэтому две параллельные транзакции, которые работают с одними и теми же данными, могут оказывать влияние друг на друга. Подробнее об этом читайте в следующей статье
Зачем нужны транзакции при работе с базой данных в Android?
Транза́кция (англ. transaction, от лат. transactio — соглашение, договор) — минимальная логически осмысленная операция, которая имеет смысл и может быть совершена только полностью.
Исходя из этого определения следует, что при обращении к чему-либо через транзакцию, при возникновении ошибки, потеря данных исключена.
Поясним это красочно на примере.
Многие из читающих знают или представляют себе, что такое транзакция в случае перевода денег. Какие проблемы могут возникнуть без транзакции?
Вы оплачиваете покупку нового телевизора через интернет банк. Нет никаких транзакций. Вы заполняете данные, поля, нажимаете кнопку отправить. В этот момент ваши деньги снимаются со счёта и уходят в сторону интернет-магазина. Но по несчастливой случайности из-за некоторой ошибки деньги не доходят. То есть они ушли в “никуда”. Требовать возврата денег не у кого, так же как и требовать телевизор у продавца, ведь денег он так и не получил.
Именно для таких случаев была придумана защита — транзакция.
В Android при работе с базой данных также рекомендуется использовать транзакции при манипулировании данными.
Есть три ключевых действия, которые мы должны совершить при проведении транзакции в Android:
- Начать транзакцию —database.beginTransaction()-
- При успешном завершении указать, что транзакция завершилась успешно —database.setTransactionSuccessful()-и завершить транзакцию -database.endTransaction()-
- При некорректном событии в транзакции (ошибке) завершить транзакцию -database.endTransaction()-
Остаётся открытым вопрос: “Как именно обрабатывать ошибки?”. Ответ прост — с помощью try-catch-finally.
Вот небольшой отрывок кода, для примера:
SomeDbHelper dbHelper = new SomeDbHelper(context);
SQLiteDatabase database = dbHelper.getWritableDatabase();
database.beginTransaction();
try ContentValues someContentValues = new ContentValues();
someContentValues.put("name", someNameString);
database.insert("Table", null, someContentValues);
database.setTransactionSuccessful();
database.endTransaction();
dbHelper.close();
>
catch (SQLiteException exception)
exception.printStackTrace();
database.endTransaction();
dbHelper.close();>
>
В данном случае, я повторюсь, при успешной вставке данных, транзакция будет успешной и данные будут в базе данных. При ошибке транзакция завершится и данные в базу данных не попадут.
Надеюсь, что кому-нибудь это будет полезно.