RoR transaction
首先看 transaction 方法
- ActiveRecord::Transactions
Public Class methods - # File vendor/rails/acti
verecord/lib/active_record/
transactions.rb, line 187 - 187: def transaction(&block)
- 188: self.class.transaction(&block)
- 189: end
可以看出他是调用类方法,类方法代码如下:
- # File vendor/rails/activerecord/lib/
active_record/transactions.rb, line 75 - def transaction(&block)
- increment_open_transactions
- begin
- #connection is kind of MysqlAdapter
- connection.transaction(Thread.current
['start_db_transaction'], &block) - ensure
- decrement_open_transactions
- end
- end
- #connection.transaction call method in
- # File vendor/rails/activerecord/
lib/active_record/connection_
adapters/abstract/database_
statements.rb, line 58 - # Wrap a block in a transaction.
Returns result of block. - def transaction(start_db_transaction = true)
- transaction_open = false
- begin
- if block_given?
- if start_db_transaction
- begin_db_transaction
- transaction_open = true
- end
- yield
- end
- rescue Exception =>
database_transaction_rollback - if transaction_open
- transaction_open = false
- rollback_db_transaction
- end
- raise unless database_transaction
_rollback.is_a? ActiveRecord::Rollback - end
- ensure
- if transaction_open
- begin
- commit_db_transaction
- rescue Exception => database
_transaction_rollback - rollback_db_transaction
- raise
- end
- end
- end
上面的代码即实现了rails中的transaction,可见ActiveRecord是不支持Ruby on Rails事物嵌套的。 如果模型使用的是相同的数据库, 那么用 ModelA.transaction 或 ModelB.transaction的作用是一样的。
- Code
- objecta.transaction do
- objectb.save!
- end
或者
- objectb.transaction do
- objecta.save!
- end
或者
- ModelA.transaction do
- objectb.save!
- end
都是一样的!
这些对象的方面或类方面, 到最后都是转换成SQL,让数据库来执行, 如果明白这个,一切都变得简单了!
就从SQL而言 "model.transaction do" 只是执行 Begin, "end" 执行Commit. 对于MYSQL个别是引挚支持的存储点功能不在本文讨论范围之内。补充一下, 目前只是SQLServer支持Ruby on Rails事物嵌套,所以如果说ROR支持事务嵌套也就有点勉强!
ActiveRecord事务
一、介绍:
在你使用Transcations API时,系统就是像在基础数据库内使用SQL BEGIN...COMMIT。
注意:基础数据库必须支持事务。对于MySQL这意味着使用InnoDB存储引擎。
每个“活动记录”对象都有一个transaction方法,它接受一个块。在BEGIN...COMMIT上下文环境内的数据库动作都在块内发生。通常在块结束时,修改被提交。如果块内引起了一个异常,所有修改会被回滚,事务中止。
数据库事务将一系列修改组合在一起,以一种方式要么完成所有修改,要么一个都不修改。
二、对象级事务:
你可以为“活动记录”对象打开对象级事务。通过将你想使用对象级事务的每个“活动记录”的名字传递给transaction()方法。像这样:
Account.transaction(david, mary) do
david.withdrawal(100)
mary.deposit(100)
end
如果事务失败,David和Mary将被返回到它的原有状态。在数据库和这两个对象内的金额均不会被修改。
也就是说,transaction也可以接受“模型”对象做为一个参数,在此情况下,“模型”将被回滚到它们的原有(事务前)状态,事务被强行中止。
记住,“活动记录”不能主动跟踪不同对象的更新前后的状态--事实上它也不可能,因为没有简单的办法知道哪个model是在事务处理当中。这就会出现这种情况:在确认失败时,数据库没有被修改,但对象却被修改了。通过使用对象级事务就可以避免这些。
在稍后的例子中,你会看到对象级事务的用法。
三、Save 和 destroy 被自动地包装在一个事务中:
Base#save 和 Base#destroy 被包装在事务中,以确保在任何时候可以确认或回滚。所以你可以使用确认来检查事务的值或者你引发一个异常来回调回滚。
在存储或删除行时“活动记录”使用同一事务技术。
四、异常处理:
也要小心在事务块中抛出的异常将会被传播(在触发回滚之后),所以你应该在你的代码中准备捕获这些异常。
例如:
peter = Account.create(:balance => 100, :number => "12345")
paul = Account.create(:balance => 200, :number => "54321")
begin
Account.transaction(peter, paul) do
paul.deposit(350)
peter.withdraw(350)
end
rescue
puts "Transfer aborted"
end
puts "Paul has #{paul.balance}"
puts "Peter has #{peter.balance}"
五、一个例子:
我们看一个在《Agile Web Development with Rails 》一书中的一个完整的例子。
1、数据库:
create table accounts (
id int not null auto_increment,
number varchar(10) not null,
balance decimal(10,2) default 0.0,
primary key (id)
) type=InnoDB;
2、模型:
class Account < ActiveRecord::Base
def withdraw(amount)
adjust_balance_and_save(-amount)
end
def deposit(amount)
adjust_balance_and_save(amount)
end
#对象级事务
def self.transfer(from, to, amount)
transaction(from, to) do
from.withdraw(amount)
to.deposit(amount)
end
end
private
def adjust_balance_and_save(amount)
self.balance += amount
save! #确认失败时引发异常
end
#确认:余额不能小于0元。
def validate
errors.add(:balance, "is negative") if balance < 0
end
end
六、应用:
peter = Account.create(:balance => 100, :number => "12345")
paul = Account.create(:balance => 200, :number => "54321")
#进行事务处理,并捕获确认失败。
Account.transfer(peter, paul, 350) rescue puts "Transfer aborted"
puts "Paul has #{paul.balance}"
puts "Peter has #{peter.balance}"
输出:
Transfer aborted
Paul has 200.0
Peter has 100.0
rails批量更新
ruby 代码 user_controller.rb
- begin
- User.transaction do
- User.update(params[:user].keys,params[:user].values)
- end
- end
rails 提供的批量更新update方法,返回一个对象数组,并不好判断保存数据是否执行成功,以下代码可以判断
ruby 代码
- begin
- User.transaction do
- params[:user].each do |id, value|
- user= User.find(id)
- user.update_attributes!(value)
- end
- end
- flash[:notice] = "保存数据成功"
- rescue
- flash[:notice] = "保存数据失败"
- end
xml 代码
这是 user.rhtm
- <% for @user in @users%>
- <td><%= text_field("user[]","name",'size'=>'10')%>td>
- <td><%= password_field("user[]","password",'size'=>'10')%>td>
- <td>
- <%=
- select("user[]","city_id",@cities.collect{|p|[p.city,p.id]},options = {:include_blank => true})
- %></td>
- <% end %>