5.4. Database access using DBIInterceptor

5.4.1. The outline of DBIInterceptor

When DBIInterceptor is aspected to a class, the method which suited Pointcut is performed. DBIInterceptor will treat the return value of a method as an SQL query, if the return value of a method is string. If the return value of a method is Array, the 1st value is treated as an SQL query, the 2nd value is treated as a context at the time of SQL execution.

The sql is executed in a database and a result is returned as a return value of a method. Prepared Statement of DBI is used for execution of the SQL query to a database. As for value bound to Prepared Statement, method arguments are used. If the return value of a method is Array, the 2nd value is treated as bind value.

DBIInterceptor does not have OR map function and automatic SQL construction function.

[Note]NOTE

This Example is located in example/example14


5.4.2. Aspect of DBIInterceptor

Let's create PrefectureDao class which accesses Prefecture table. The find_all method which gets all the data from Prefecture table is implemented in PrefectureDao class. As a return value of the find_all method, SQL query which will be executed to a database is returned.

module Example
  class PrefectureDao
    s2comp
    s2aspect :interceptor => "dbi.interceptor"
    def find_all
      return 'select * from prefecture'
    end
  end
end

Please create run.rb which performs the next processing.

  1. Seasar::DBI::DBIInterceptor can be used By requiring 'seasar/dbi/interceoptor'. "dbi.interceptor" component is registered when Seasar::DBI::Interceptor class definition is read.
  2. DBI::DatabaseHandle class is registered as a component by the s2component method. It is necessary to set namespace of a component to "dbi" same with DBIInterceptor. DSN is set up by the constructor block passed to the s2component method.
  3. Getting PrefectureDao component and calling find_all mehod. DBI::StatementHandle.fetch_hash result is returned to the return value of DBIInterceptor.
require 'rubygems'
require 'dbi'
require 's2container'
require 'seasar/dbi/interceptor'
require 'example'

s2comp(:class => DBI::DatabaseHandle, :namespace => "dbi", :autobinding => :none) {
  DBI.connect("dbi:SQLite3:example.db")
}

begin
  s2app[Example::PrefectureDao].find_all.each {|h|
    puts "#{h['id']}\t#{h['name']}"
  }
rescue Seasar::DBI::DBIInterceptor::ConnectError => e
  s2logger.fatal(e.cause.class.name){"#{e.cause.message} #{e.cause.backtrace}"}
rescue => e
  s2logger.fatal(e.class.name){"#{e.message} #{e.backtrace}"}
ensure
  s2app[DBI::DatabaseHandle].disconnect if s2app[DBI::DatabaseHandle]

end

5.4.3. Setup Bind value by Method argument

Let's create PrefectureDao class which accesses a Prefecture table. The find_by_id method which searches data with ID from Prefecture table is implemented in PrefectureDao class. ID which searches data is specified by the argument of find_by_id method. The embedding of ID to an SQL query is described like "?".

module Example
  class PrefectureDao
    s2comp
    s2aspect :interceptor => "dbi.interceptor"
    def find_by_id(id)
      return 'select * from prefecture where id = ?'
    end
  end
end

Please create the following executable files.

require 'rubygems'
require 'dbi'

require 's2container'
require 'seasar/dbi/interceptor'
require 'example'

s2comp(:class => DBI::DatabaseHandle, :namespace => "dbi", :autobinding => :none) {
  DBI.connect("dbi:SQLite3:example.db")
}

begin
  s2app[Example::PrefectureDao].find_by_id(1).each {|h|
    puts "#{h['id']}\t#{h['name']}"
  }
rescue Seasar::DBI::DBIInterceptor::ConnectError => e
  s2logger.fatal(e.cause.class.name){"#{e.cause.message} #{e.cause.backtrace}"}
rescue => e
  s2logger.fatal(e.class.name){"#{e.message} #{e.backtrace}"}
ensure
  s2app[DBI::DatabaseHandle].disconnect if s2app[DBI::DatabaseHandle]

end

5.4.4. Setup Bind value By Method result

Let's create PrefectureDao class which accesses a Prefecture table. The find_by_name method which searches data with NAME from Prefecture table is implemented in PrefectureDao class. Array is returned as a return value of a method, the 1st value is treated as an SQL query, the 2nd value is treated as a context at the time of SQL execution.

module Example
  class PrefectureDao
    s2comp
    s2aspect :interceptor => "dbi.interceptor"
    def find_by_name(name)
      return "select * from prefecture where name = ?", name
    end
  end
end

Please create the following executable files.

require 'rubygems'
require 'dbi'

require 's2container'
require 'seasar/dbi/interceptor'
require 'example'

s2comp(:class => DBI::DatabaseHandle, :namespace => "dbi", :autobinding => :none) {
  DBI.connect("dbi:SQLite3:example.db")
}

begin
  s2app[Example::PrefectureDao].find_by_name("HOKKAIDO").each {|h|
    puts "#{h['id']}\t#{h['name']}"
  }
rescue Seasar::DBI::DBIInterceptor::ConnectError => e
  s2logger.fatal(e.cause.class.name){"#{e.cause.message} #{e.cause.backtrace}"}
rescue => e
  s2logger.fatal(e.class.name){"#{e.message} #{e.backtrace}"}
ensure
  s2app[DBI::DatabaseHandle].disconnect if s2app[DBI::DatabaseHandle]

end

5.4.5. Using DBI in Dao

If using DBI directly in Dao, It is necessary to carry out injection of the DBI component to Dao component. In the following example, Property injection of the DBI::DatabaseHandle component is executed to the PrefectureDao class. In the transactional_insert method, the transaction is started using injected DBI component.

module Example
  class PrefectureDao
    s2comp
    s2aspect :interceptor => "dbi.interceptor", :pointcut => /^insert/

    def initialize
      @dbh = :di, DBI::DatabaseHandle
    end

    def transactional_insert(id, name)
      result = nil
      @dbh['AutoCommit'] = false
      begin
        result = self.insert(id, name)
        @dbh.commit
      rescue => e
        s2logger.warn(self.class.superclass.name) { "transaction failed. #{e.class.name} #{e.message}" }
        @dbh.rollback
      end
      @dbh['AutoCommit'] = true
      return result
    end

    def insert(id, name)
      return 'insert into prefecture values(?, ?)'
    end
  end
end

Please create the following executable files.

require 'rubygems'
require 'dbi'

require 's2container'
require 'seasar/dbi/interceptor'
require 'example'

s2comp(:class => DBI::DatabaseHandle, :namespace => "dbi", :autobinding => :none) {
  DBI.connect("dbi:SQLite3:example.db")
}

begin
  puts s2app[Example::PrefectureDao].transactional_insert(48, "Other")
rescue Seasar::DBI::DBIInterceptor::ConnectError => e
  s2logger.fatal(e.cause.class.name){"#{e.cause.message} #{e.cause.backtrace}"}
rescue => e
  s2logger.fatal(e.class.name){"#{e.message} #{e.backtrace}"}
ensure
  s2app[DBI::DatabaseHandle].disconnect if s2app[DBI::DatabaseHandle]

end

5.4.6. Paging by Paginate class

Seasar::DBI::Paginate class is a utility class which performs paging when acquiring data from Database. Let's implement the find_by_dto method in PrefectureDao class and do paging.

module Example
  class PrefectureDao
    s2comp
    s2aspect :interceptor => "dbi.interceptor"

    def find_by_dto(dto)
      return "select * from prefecture where name like ? limit ? offset ?", [dto.name_like, dto.limit, dto.offset]
    end

    def find_total_by_dto(dto)
      return "select count(*) as total from prefecture where name like ?", dto.name_like
    end
  end

  require 'seasar/dbi/paginate'
  class PrefectureDto < Seasar::DBI::Paginate
    attr_accessor :name_like
  end
end

Please create run.rb which performs the next processing.

  1. create a instance of Example::PrefectureDto
  2. set the page limit as 5
  3. set the search information
  4. set the total count
  5. search the 1st page data and display the result
  6. move to next page
  7. search the 2nd page data and display the result
require 'rubygems'
require 'dbi'

require 's2container'
require 'seasar/dbi/interceptor'
require 'example'

s2comp(:class => DBI::DatabaseHandle, :namespace => "dbi", :autobinding => :none) {
  DBI.connect("dbi:SQLite3:example.db")
}

begin
  dto = Example::PrefectureDto.new
  dto.limit = 5
  dto.name_like = '%er'
  dto.total = s2app[Example::PrefectureDao].find_total_by_dto(dto)[0]["total"]
  puts "page : #{dto.page}"
  s2app[Example::PrefectureDao].find_by_dto(dto).each {|h|
    puts "#{h['id']}\t#{h['name']}"
  }
  dto.next
  puts "page : #{dto.page}"
  s2app[Example::PrefectureDao].find_by_dto(dto).each {|h|
    puts "#{h['id']}\t#{h['name']}"
  }
rescue Seasar::DBI::DBIInterceptor::ConnectError => e
  s2logger.fatal(e.cause.class.name){"#{e.cause.message} #{e.cause.backtrace}"}
rescue => e
  s2logger.fatal(e.class.name){"#{e.message} #{e.backtrace}"}
ensure
  s2app[DBI::DatabaseHandle].disconnect if s2app[DBI::DatabaseHandle]

end

5.4.6.1. Paginate API Reference

Seasar::DBI::Paginate.get_total_page method. 

All the number of pages is returned. (total records / page limit)

Seasar::DBI::Paginate#paage method. 

Current page number is returned.

Seasar::DBI::Paginate#page= method. 

Page number is set up.

Seasar::DBI::Paginate#offset method. 

Current offset position is returned.

Seasar::DBI::Paginate#limit= method. 

The number of records per 1 Paige is set up.

Seasar::DBI::Paginate#total method. 

The total number is returned.

Seasar::DBI::Paginate#total= method. 

The total number is set up.

Seasar::DBI::Paginate#window_size method. 

The number of Pages displayed on a window is set up.。

Seasar::DBI::Paginate#next method. 

1 page is advanced.

Seasar::DBI::Paginate#next? method. 

It is returned whether there is any next page.

Seasar::DBI::Paginate#prev method. 

1 page returns.

Seasar::DBI::Paginate#prev? method. 

It is returned whether there is any previous page.

Seasar::DBI::Paginate#page_range method. 

Range of page number settled in a page window is returned.



© Copyright The Seasar Foundation and the others 2008-2009, all rights reserved.