RoR transaction

首先看 transaction 方法

  1. ActiveRecord::Transactions 
    Public Class methods  
  2. # File vendor/rails/acti
    verecord/lib/active_record/
    transactions.rb, line 187  
  3. 187: def transaction(&block)  
  4. 188: self.class.transaction(&block)  
  5. 189: end 

可以看出他是调用类方法,类方法代码如下:

 

  1. # File vendor/rails/activerecord/lib/
    active_record/transactions.rb, line 75  
  2. def transaction(&block)  
  3. increment_open_transactions  
  4. begin  
  5. #connection is kind of MysqlAdapter  
  6. connection.transaction(Thread.current
    ['start_db_transaction'], &block)  
  7. ensure  
  8. decrement_open_transactions  
  9. end  
  10. end  
  11. #connection.transaction call method in  
  12. # File vendor/rails/activerecord/
    lib/active_record/connection_
    adapters/abstract/database_
    statements.rb, line 58  
  13. # Wrap a block in a transaction. 
    Returns result of block.  
  14. def transaction(start_db_transaction = true)  
  15. transaction_open = false 
  16. begin  
  17. if block_given?  
  18. if start_db_transaction  
  19. begin_db_transaction  
  20. transaction_open = true 
  21. end  
  22. yield  
  23. end  
  24. rescue Exception =>
     database_transaction_rollback  
  25. if transaction_open  
  26. transaction_open = false 
  27. rollback_db_transaction  
  28. end  
  29. raise unless database_transaction
    _rollback.is_a? ActiveRecord::Rollback  
  30. end  
  31. ensure  
  32. if transaction_open  
  33. begin  
  34. commit_db_transaction  
  35. rescue Exception => database
    _transaction_rollback  
  36. rollback_db_transaction  
  37. raise  
  38. end  
  39. end  
  40. end 

 

上面的代码即实现了rails中的transaction,可见ActiveRecord是不支持Ruby on Rails事物嵌套的。 如果模型使用的是相同的数据库, 那么用 ModelA.transaction 或 ModelB.transaction的作用是一样的。

 

  1. Code  
  2. objecta.transaction do  
  3. objectb.save!  
  4. end 

或者

  1. objectb.transaction do  
  2. objecta.save!  
  3. end 

或者

  1. ModelA.transaction do  
  2. objectb.save!  
  3. 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

  1. begin
  2. User.transaction do
  3.    User.update(params[:user].keys,params[:user].values)   
  4. end
  5. end

rails 提供的批量更新update方法,返回一个对象数组,并不好判断保存数据是否执行成功,以下代码可以判断

ruby 代码

  1. begin
  2.   User.transaction do
  3.     params[:user].each do |id, value|      
  4.       user= User.find(id)      
  5.       user.update_attributes!(value)      
  6. end
  7. end
  8.   flash[:notice] = "保存数据成功"
  9. rescue
  10.   flash[:notice] = "保存数据失败"
  11. end

xml 代码

这是 user.rhtm

  1. <% for @user in @users%>
  2. <td><%= text_field("user[]","name",'size'=>'10')%>td>
  3. <td><%= password_field("user[]","password",'size'=>'10')%>td>
  4. <td>
  5. <%=   
  6.     select("user[]","city_id",@cities.collect{|p|[p.city,p.id]},options = {:include_blank => true})   
  7.    %></td>
  8. <% end %>
posted @ 2012-09-27 14:55  残星  阅读(1189)  评论(0编辑  收藏  举报