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