12/08/2018, 14:01

Transaction trong Rails

Transaction giúp toàn vẹn dữ liệu, các thay đổi trong cơ sở dữ liệu chỉ được giữ lại khi tất cả các câu lệnh SQL trong transaction đều được thực hiện thành công. Vậy nên ta sẽ dùng transaction khi có 1 số thao tác với cơ sở dữ liệu mà yêu cầu tất cả các thao tác đó đều phải được thực hiện thành ...

Transaction giúp toàn vẹn dữ liệu, các thay đổi trong cơ sở dữ liệu chỉ được giữ lại khi tất cả các câu lệnh SQL trong transaction đều được thực hiện thành công. Vậy nên ta sẽ dùng transaction khi có 1 số thao tác với cơ sở dữ liệu mà yêu cầu tất cả các thao tác đó đều phải được thực hiện thành công.

Ví dụ như chuyển khoản chẳng hạn, tài khoản A chuyển tiền cho tài khoản B thì chúng ta phải thực hiện ít nhất 2 thao tác là trừ tiền ở tài khoản A và cộng tiền vào tài khoản B. Nếu 1 trong 2 thao tác bị thất bại thì phải huỷ thao tác còn lại để đảm bảo dữ liệu vẫn chính xác.

ActiveRecord::Base.transaction do
  a.dec 1000
  b.inc 1000
end
# cả 2 thao tác đều phải được thực hiện thành công

Tuy nhiên, nếu không thành công, transaction chỉ có tác dụng phục hồi lại trạng thái cơ sở dữ liệu trước khi thực hiện thao tác, chứ không phục hồi trạng thái cuả các đối tượng liên quan.

Có thể goị transaction từ một lớp được kế thừa từ ActiveRecord::Base, hoặc cũng có thể gọi transaction từ 1 thể hiện của Model. Các đối tượng/lớp bên trong transaction không nhất thiết phải là thể hiện của Model gọi transaction.

Account.transaction do
  a.dec 1000
  b.inc 1000
  Invoice.create! from: a, to: b, amount: 1000
end

Transaction không làm việc với nhiều kết nối cơ sở dữ liệu khác nhau

Transaction chỉ làm việc với một kết nối cơ sở dữ liệu duy nhất. Nếu bạn phải thao tác với nhiều kết nối cơ sở dữ liệu khác nhau, transaction sẽ không thể đảm bảo sự tương tác giữa chúng. Một giải pháp cho vấn đề này đó là gọi transaction ở mỗi classs của Model mà bạn phải làm việc.

Ví dụ:

Student.transaction do
  Course.transaction do
    course.enroll student
    student.units += course.units
  end
end

save và destroy tự động được gói lại trong 1 transaction

Nếu save hoặc destroy thất bại do validation, hay có Exception được raise trong các callback (kể cả after _ save và after _ destroy) các thay đổi với database sẽ bị huỷ bỏ.

Ví dụ:

# apps/models/user.rb
class User < ApplicationRecord
  .
  .
  .
  has_secure_password
  validates :password, presence: true, length: {minimum: 6}
  .
  .
  .
end

Chúng ta tạo ra 1 thể hiện:

user = User.new name: "My Name", email: "MyEmail@gmail.com", password: "123456",
  password_confirmation: "1234567"

Khi thực hiện lệnh user.save, sẽ có lỗi xảy ra do password và password_confirmation không trùng khớp. Thao tác lưu sẽ bị huỷ bỏ.

Với user hợp lệ:

user = User.new name: "My Name", email: "MyEmail@gmail.com", password: "123456",
  password_confirmation: "123456"

thì khi gọi user.save, thông tin của user sẽ được lưu vào database.

Xử lý ngoại lệ và rollback

Khi có lỗi xảy ra trong một transaction, chỉ có ngoại lệ ActiveRecord::Rollback là không được ném ra ngoài, nhiệm vụ của nó là sẽ gọi ROLLBACK đưa cơ sở dữ liệu về trạng thái trước khi thực hiện transacion. Còn các ngoại lệ khác sẽ được "ném" ra ngoài transaction sau khi gọi ROLLBACK được gọi.

Bởi vậy, ta phải "bắt" các ngoại lệ này để xử lý.

Active::Base.transaction do
  # các thao tác với cơ sở dữ liệu
end
rescue
  # xử lý ngoại lệ

Transaction lồng nhau

Các transaction có thể lồng vào nhau ,khi đó, các câu lệnh tác động đến cơ sở dữ liệu nằm trong transaction bên trong sẽ trở thành 1 phần của transaction bên ngoài.

User.transaction do
  User.create username: "Nga"
  User.transaction do
    User.create username: "Nam"
    raise ActiveRecord::Rollback
  end
end

Với ví dụ trên sẽ tạo ra cả 2 user Nga và Nam trong cơ sở dữ liệu. Bởi User.create username: "Nam" đã trở thành 1 phần của transaction bên ngoài còn ngoại lệ được transaction bên trong bắt nên transaction bên ngoài không bị ảnh hưởng gì.

Để ROLLBACK cho transaction bên trong, ta cần thêm requires_new: true khi gọi transaction.

User.transaction do
  User.create username: "Nga"
  User.transaction(requires_new: true) do
    User.create username: "Nam"
    raise ActiveRecord::Rollback
  end
end

Như vậy, trong cơ sở dữ liệu chỉ có user Nga được tạo ra, còn user Nam đã bị ROLLBACK xoá đi.

Callbacks

Có 2 loại callback được gắn với transaction khi commit và rollback, đó là: after_commit va after_rollback.

  • after_commit sẽ được gọi mỗi khi có bản ghi bên trong transaction được lưu lại hoặc xoá khỏi CSDL khi transaction được commit.

  • after_rollback sẽ được gọi mỗi khi có bản ghi bên trong transaction được lưu lại hoặc xoá khỏi CSDL khi transaction được rollback.

Trên đây là những kiến thức về transaction em đã tìm hiểu được. Do mới nghiên cứ về Rails nên có thể có nhiều sai sót. Mong nhận được sự góp ý của mọi người.

Thanks for reading!

Tham khảo: http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html

0