Ruby on Rails
Transakcje ActiveRecord
Szukaj…
Uwagi
Transakcje są blokami ochronnymi, w których instrukcje SQL są trwałe tylko wtedy, gdy wszystkie mogą odnieść sukces jako jedna akcja atomowa. Klasycznym przykładem jest przelew między dwoma kontami, na którym można wpłacić depozyt tylko wtedy, gdy wypłata się powiodła i na odwrót. Transakcje wymuszają integralność bazy danych i chronią dane przed błędami programu lub awariami bazy danych. Zasadniczo powinieneś używać bloków transakcji, ilekroć masz wiele instrukcji, które muszą być wykonane razem lub wcale.
Podstawowy przykład
Na przykład:
ActiveRecord::Base.transaction do
david.withdrawal(100)
mary.deposit(100)
end
Ten przykład zabierze Dawidowi pieniądze i przekaże je Maryi, jeśli ani wypłata, ani depozyt nie spowodują wyjątku. Wyjątki wymuszą ROLLBACK, który przywraca bazę danych do stanu przed rozpoczęciem transakcji. Należy jednak pamiętać, że dane obiektów nie zostaną przywrócone do stanu sprzed transakcji.
Różne klasy ActiveRecord w jednej transakcji
Chociaż metoda klasy transakcji jest wywoływana w przypadku niektórych klas ActiveRecord, wszystkie obiekty w bloku transakcji nie muszą być instancjami tej klasy. Wynika to z faktu, że transakcje dotyczą połączenia dla bazy danych, a nie dla modelu.
W tym przykładzie rekord salda jest zapisywany transakcyjnie, mimo że transakcja jest wywoływana w klasie Account:
Account.transaction do
balance.save!
account.save!
end
Metoda transakcji jest również dostępna jako metoda instancji modelu. Na przykład możesz to zrobić:
balance.transaction do
balance.save!
account.save!
end
Wiele połączeń z bazą danych
Transakcja działa na pojedyncze połączenie z bazą danych. Jeśli masz wiele baz danych specyficznych dla klasy, transakcja nie ochroni interakcji między nimi. Jednym obejściem jest rozpoczęcie transakcji na każdej klasie, której modele zmienisz:
Student.transaction do
Course.transaction do
course.enroll(student)
student.units += course.units
end
end
To kiepskie rozwiązanie, ale w pełni rozproszone transakcje wykraczają poza zakres ActiveRecord.
zapisz i zniszcz są automatycznie pakowane w transakcję
Zarówno #save, jak i #destroy są pakowane w transakcję, która gwarantuje, że wszystko, co zrobisz podczas sprawdzania poprawności lub wywołania zwrotnego, będzie miało miejsce pod ochroną. Możesz więc użyć sprawdzania poprawności do sprawdzenia wartości, od których zależy transakcja, lub możesz zgłosić wyjątki w wywołaniach zwrotnych do wycofania, w tym wywołania zwrotne after_*
.
W rezultacie zmiany w bazie danych nie są widoczne poza Twoim połączeniem, dopóki operacja nie zostanie zakończona. Na przykład, jeśli spróbujesz zaktualizować indeks wyszukiwarki w after_save
indeksator nie zobaczy zaktualizowanego rekordu. Callback after_commit
jest jedynym wyzwalanym po zatwierdzeniu aktualizacji.
Callbacki
Istnieją dwa rodzaje wywołań zwrotnych związanych z after_commit
i after_rollback
transakcji: after_commit
i after_rollback
.
wywołania zwrotne after_commit
są wywoływane dla każdego rekordu zapisanego lub zniszczonego w transakcji natychmiast po zatwierdzeniu transakcji. wywołania zwrotne after_rollback
są wywoływane przy każdym rekordzie zapisanym lub zniszczonym w transakcji natychmiast po wycofaniu transakcji lub punktu zapisu.
Te wywołania zwrotne są przydatne do interakcji z innymi systemami, ponieważ gwarantujemy, że wywołanie zwrotne jest wykonywane tylko wtedy, gdy baza danych jest w stanie stałym. Na przykład after_commit
jest dobrym miejscem do zaczepienia się o wyczyszczenie pamięci podręcznej, ponieważ wyczyszczenie jej w ramach transakcji może spowodować after_commit
pamięci podręcznej przed zaktualizowaniem bazy danych.
Cofanie transakcji
ActiveRecord::Base.transaction
korzysta z wyjątku ActiveRecord::Rollback
aby odróżnić celowe wycofywanie od innych wyjątkowych sytuacji. Zwykle zgłoszenie wyjątku spowoduje wycofanie transakcji bazy danych przez metodę .transaction
i przekazanie wyjątku. Ale jeśli ActiveRecord::Rollback
wyjątek ActiveRecord::Rollback
, transakcja bazy danych zostanie wycofana bez przekazywania wyjątku.
Na przykład możesz to zrobić w kontrolerze, aby wycofać transakcję:
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