这次我们分析一下Rails的事务支持
1,Rails默认将父子关系的表的save()和destroy()包装在一个事务里(见AWDWR一书的Transactions)
这保证了父子保存和删除的原子性,即ActiveRecord是级联保存和级联删除的,有源码为证
transactions.rb:
module ActiveRecord
module Transactions
def self.included(base)
base.extend(ClassMethods)
base.class_eval do
[:destroy, :save, :save!].each do |method|
alias_method_chain method, :transactions
end
end
end
module ClassMethods
def transaction(*objects, &block)
previous_handler = trap('TERM') { raise TransactionError, "Transaction aborted" }
increment_open_transactions
begin
unless objects.empty?
ActiveSupport::Deprecation.warn "Object transactions are deprecated and will be removed from Rails 2.0. See http://www.rubyonrails.org/deprecation for details.", caller
objects.each { |o| o.extend(Transaction::Simple) }
objects.each { |o| o.start_transaction }
end
result = connection.transaction(Thread.current['start_db_transaction'], &block)
objects.each { |o| o.commit_transaction }
return result
rescue Exception => object_transaction_rollback
objects.each { |o| o.abort_transaction }
raise
ensure
decrement_open_transactions
trap('TERM', previous_handler)
end
end
private
def increment_open_transactions #:nodoc:
open = Thread.current['open_transactions'] ||= 0
Thread.current['start_db_transaction'] = open.zero?
Thread.current['open_transactions'] = open + 1
end
def decrement_open_transactions #:nodoc:
Thread.current['open_transactions'] -= 1
end
end
def transaction(*objects, &block)
self.class.transaction(*objects, &block)
end
end
end
mysql_adapter.rb:
module ActiveRecord
module ConnectionAdapters
class MysqlAdapter < AbstractAdapter
def begin_db_transaction #:nodoc:
execute "BEGIN"
rescue Exception
# Transactions aren't supported
end
def commit_db_transaction #:nodoc:
execute "COMMIT"
rescue Exception
# Transactions aren't supported
end
def rollback_db_transaction #:nodoc:
execute "ROLLBACK"
rescue Exception
# Transactions aren't supported
end
end
end
end
如果我们想给自定义的方法添加事务控制,有如下三种情况:
1,block transaction
def some_method
transaction do
david.withdrawal(100)
mary.deposit(100)
end
end
transaction方法后的block里的操作保持原子性
2,Object-level transaction
def some_method
Account.transaction(from, to) do
from.withdraw(100)
to.deposit(100)
end
end
这种情况下不仅数据库表有事务回滚,对象状态也有事务回滚
不过现在
Rails去掉object transactions
如果你仍然想使用object transactions,可以使用
object_transactions插件
3,Across database connections
def some_method
Student.transaction do
Course.transaction do
course.enroll(student)
student.units += course.units
end
end
end
但是这样做很难保证不同表的状态,ActiveRecord也不打算做multiple database的transaction,建议不要使用这种方式
我们再看看用到transactions的一些地方
association_collection.rb:
module ActiveRecord
module Associations
class AssociationCollection < AssociationProxy
def <<(*records)
result = true
load_target
@owner.transaction do
flatten_deeper(records).each do |record|
raise_on_type_mismatch(record)
callback(:before_add, record)
result &&= insert_record(record) unless @owner.new_record?
@target << record
callback(:after_add, record)
end
end
result && self
end
end
end
end
has_many_through_association.rb:
module ActiveRecord
module Associations
class HasManyThroughAssociation < AssociationProxy
def create!(attrs = nil)
@reflection.klass.transaction do
self << @reflection.klass.with_scope(:create => attrs) { @reflection.klass.create! }
end
end
end
end
end