Ruby on Rails
Активные транзакции ActiveRecord
Поиск…
замечания
Транзакции являются защитными блоками, где заявления SQL являются только постоянными, если все они могут быть успешными как одно атомное действие. Классический пример - это передача между двумя учетными записями, где вы можете получить только депозит, если списание отменено, и наоборот. Транзакции обеспечивают целостность базы данных и защищают данные от ошибок программы или разбивки баз данных. Поэтому в основном вы должны использовать блоки транзакций, когда у вас есть несколько операторов, которые должны выполняться вместе или вообще не выполняться.
Основной пример
Например:
ActiveRecord::Base.transaction do
david.withdrawal(100)
mary.deposit(100)
end
Этот пример будет брать деньги только у Дэвида и отдать его Мэри, если ни отход, ни депозит не приведут к исключению. Исключения заставят ROLLBACK вернуть базу данных в состояние до начала транзакции. Однако имейте в виду, что у объектов не будет данных экземпляра, возвращаемых в их предварительное транзакционное состояние.
Различные классы ActiveRecord в одной транзакции
Хотя метод класса транзакции вызывается в некотором классе ActiveRecord, объекты внутри блока транзакций не обязательно должны быть экземплярами этого класса. Это связано с тем, что транзакции - это подключение по каждой базе данных, а не для каждой модели.
В этом примере запись баланса сохраняется в транзакции, даже если транзакция вызывается в классе Account:
Account.transaction do
balance.save!
account.save!
end
Метод транзакции также доступен как метод экземпляра модели. Например, вы также можете сделать это:
balance.transaction do
balance.save!
account.save!
end
Несколько соединений с базой данных
Транзакция действует на одно соединение с базой данных. Если у вас несколько баз данных, специфичных для класса, транзакция не будет защищать взаимодействие между ними. Одним из способов является начало транзакции для каждого класса, чьи модели вы изменяете:
Student.transaction do
Course.transaction do
course.enroll(student)
student.units += course.units
end
end
Это плохое решение, но полностью распределенные транзакции выходят за рамки ActiveRecord.
сохранение и уничтожение автоматически завертываются в транзакцию
Оба метода #save и #destroy завершаются транзакцией, которая гарантирует, что все, что вы делаете при проверке или обратном вызове, произойдет под защищенной оболочкой. Таким образом, вы можете использовать проверки для проверки значений, на которые зависит транзакция, или вы можете after_*
исключения в обратных after_*
для отката, включая after_*
callbacks.
Как следствие, изменения в базе данных не отображаются за пределами вашего соединения до завершения операции. Например, если вы попытаетесь обновить индекс поисковой системы в after_save
не увидит обновленную запись. after_commit
вызов after_commit
является единственным, который запускается после завершения обновления.
Callbacks
Существует два типа обратных вызовов, связанных с транзакциями и after_rollback
транзакций: after_commit
и after_rollback
.
after_commit
callbacks after_commit
для каждой записи, сохраненной или уничтоженной в транзакции сразу после совершения транзакции. after_rollback
вызовы after_rollback
вызываются в каждой записи, сохраненной или уничтоженной в транзакции сразу после откат транзакции или точки сохранения.
Эти обратные вызовы полезны для взаимодействия с другими системами, поскольку вам гарантируется, что обратный вызов будет выполняться только в том случае, если база данных находится в постоянном состоянии. Например, after_commit
- хорошее место, чтобы положить крючок в очистку кеша, поскольку очистка его изнутри транзакции может привести к восстановлению кэша до обновления базы данных.
Откат транзакции
ActiveRecord::Base.transaction
использует исключение ActiveRecord::Rollback
чтобы отличить преднамеренный откат от других исключительных ситуаций. Обычно повышение исключения приводит к .transaction
метод .transaction
откатывается от транзакции базы данных и передает исключение. Но если вы ActiveRecord::Rollback
исключение ActiveRecord::Rollback
, транзакция базы данных будет отброшена, не передавая исключение.
Например, вы можете сделать это в своем контроллере для отката транзакции:
class BooksController < ActionController::Base
def create
Book.transaction do
book = Book.new(params[:book])
book.save!
if today_is_friday?
# The system must fail on Friday so that our support department
# won't be out of job. We silently rollback this transaction
# without telling the user.
raise ActiveRecord::Rollback, "Call tech support!"
end
end
# ActiveRecord::Rollback is the only exception that won't be passed on
# by ActiveRecord::Base.transaction, so this line will still be reached
# even on Friday.
redirect_to root_url
end
end