JSONデータとして取得したDBの検索結果をExtJSで表示するWebシステムのサンプル - Sinatra, Grails, CakePHP
DB の検索結果を JSON で返して、JavaScript(ExtJS)で表示するような Web システムのサンプルを Sinatra, Grails, CakePHP の 3種類のフレームワークで作成してみました。
処理の概要は以下の通りで、MySQL の information_schema データベース TABLES テーブルに対する検索結果を ExtJS で表示します。
- サーバー処理
- (1) table_schema の一覧を JSON で返す(URL は informations/databases)
- (2) 指定 table_schema に属するテーブル情報の一覧を JSON で返す(URL は informations/tables/
)
- クライアント処理
- (1) の結果をドロップダウンリストへ表示
- ドロップダウンリストで table_schema を選択した際に (2) を取得
- (2) の結果を GridPanel へ表示
なお、クライアント側は共通の HTML ファイルを使用するようにしましたが、JSON の日付フォーマットに違いがあったため、その部分だけが異なっています。
Sinatra の場合
今回は、JRuby を使用し、DB検索に Sequel、JSON 変換に ActiveSupport を使いました。
なお、Sequel はモデルクラスを使わずに SQL をそのまま実行するようにしています。
- JRuby 1.5.1
- Sinatra 1.0
- Sequel 3.13.0
- ActiveSupport 2.3.8
コントローラー table_list.rb
require "rubygems" require "sinatra" require "sequel" require "mysql-connector-java-5.1.6-bin.jar" require "active_support" DB = Sequel.connect("jdbc:mysql://localhost/information_schema?user=root") # (1) table_schema の一覧取得 get '/informations/databases' do DB["SELECT DISTINCT table_schema FROM tables ORDER BY table_schema"].to_json end # (2) 指定 table_schema に属する table の一覧取得 get '/informations/tables/:table_schema' do |t| sql = "SELECT table_name, table_type, engine, avg_row_length, create_time FROM tables WHERE table_schema=?" DB[sql, t].to_json end
Web ページの実装は以下の通り。初期化時に取得した table_schema の一覧をドロップダウンリスト(Ext.form.ComboBox)に表示、table_schema を選択した際に、table 情報を取得して Ext.grid.GridPanel に表示します。
Webページ public/index.html
<!DOCTYPE html> <html> <head> <link rel="stylesheet" type="text/css" href="ext-3.2.1/resources/css/ext-all.css" /> <script src="ext-3.2.1/adapter/ext/ext-base.js" type="text/javascript"></script> <script src="ext-3.2.1/ext-all.js" type="text/javascript"></script> <script type="text/javascript"> Ext.onReady(function() { //JSONデータ(table 情報)の日付フォーマット var dformat = "Y/m/d G:i:s O"; var dbStore = new Ext.data.JsonStore({ url: "informations/databases", fields: ["table_schema"] }); //table_schema の一覧を取得 dbStore.load(); //ドロップダウンリストの定義 var dbList = new Ext.form.ComboBox({ store: dbStore, displayField: "table_schema", mode: "local", autoWidth: true, triggerAction : 'all', editable: false, applyTo: "dbList" }); var tableStore = new Ext.data.JsonStore({ //proxy を自動設定するためにダミーのURLを設定 url: "informations/tables/0", fields: [ "table_name", "table_type", "engine", {name: "avg_row_length", type: "int"}, {name: "create_time", type: "date", dateFormat: dformat} ] }); //table 情報を表示するための var grid = new Ext.grid.GridPanel({ store: tableStore, columns: [ {dataIndex: "table_name", header: "TABLE_NAME", sortable: true}, {dataIndex: "table_type", header: "TABLE_TYPE", sortable: true}, {dataIndex: "engine", header: "ENGINE", sortable: true}, {dataIndex: "avg_row_length", header: "AVG_ROW_LENGTH", sortable: true, width: 130}, {id: "create_time", dataIndex: "create_time", header: "CREATE_TIME", sortable: true, renderer: Ext.util.Format.dateRenderer('Y/m/d G:i:s')} ], width: 600, height: 200, applyTo: "tableList", autoExpandColumn: "create_time", stripeRows: true }); dbList.on("select", function(combo, record, index) { //URL の変更(選択した table_schema を組み込んだ URL を設定) tableStore.proxy.setUrl("informations/tables/" + record.data["table_schema"]); //table 情報を取得 tableStore.load(); }); }); </script> </head> <body> <div> <input type="text" id="dbList" /> </div> <div id="tableList"></div> </body> </html>
Grails の場合
Grails を使って上記と同様の処理を実装してみます。
- Groovy 1.7.3
- Grails 1.3.2
まず、MySQL の information_schema に接続するための設定を実施します。
DB設定 grails-app/conf/DataSource.groovy
dataSource { pooled = true driverClassName = "com.mysql.jdbc.Driver" } hibernate { cache.use_second_level_cache = true cache.use_query_cache = true cache.provider_class = 'net.sf.ehcache.hibernate.EhCacheProvider' dialect = 'org.hibernate.dialect.MySQLDialect' // show_sql = true } // environment specific settings environments { development { dataSource { url = "jdbc:mysql://localhost/information_schema?user=root" } } ・・・ }
次にドメインクラス(モデル)を定義します。ドメインクラスではクライアントの処理に合わせて DB のフィールド名をそのまま変数名(プロパティ名)に使いました。id が未設定の場合にエラーが発生したため、適当に設定しています。(今回の用途では使用しないので問題なし)
モデル grails-app/domain/fits/sample_mysql/Table.groovy
package fits.sample_mysql import java.sql.Timestamp class Table { static constraints = { } static mapping = { //テーブル名の設定 table "tables" //id は適当に設定しておく id name: "table_name" version false } String table_schema String table_name String table_type String engine int avg_row_length Timestamp create_time }
コントローラー側では、ドメインクラスを検索し、その結果を JSON 化します。
なお、Criteria を使い "SELECT DISTINCT table_schema FROM tables" が実行されるようにしています。
また、table 情報を取得するためのURL "informations/tables/
コントローラー grails-app/controllers/fits/sample_mysql/InformationsController.groovy
package sample_mysql import grails.converters.* class InformationsController { // (1) table_schema の一覧取得 def databases = { def c = Table.createCriteria() //table_schema を検索 def result = c.list { projections { distinct("table_schema") } order("table_schema") } //table_schema をキーにした連想配列の配列に変換 result = result.collect { [table_schema: it] } render result as JSON } // (2) 指定 table_schema に属する table の一覧取得 //指定した table_schema の値は params.id で取得する def tables = { def result = Table.findAllByTable_schema(params.id) render result as JSON } }
Web ページの実装は以下の通り。
ActiveSupport で作成した JSON とは日付のフォーマットが異なるために、dformat の値を変更しています。
Webページ web-app/index.html
<!DOCTYPE html> <html> ・・・ <script type="text/javascript"> Ext.onReady(function() { //JSONデータの日付フォーマット var dformat = "c"; ・・・ }); </script> ・・・ </html>
CakePHP の場合
最後に CakePHP で実装してみます。基本的な実装の仕方は Grails と同じです。
DB設定 app/config/database.php
<?php class DATABASE_CONFIG { var $default = array( 'driver' => 'mysql', 'persistent' => false, 'host' => 'localhost', 'login' => 'root', 'password' => '', 'database' => 'information_schema', 'prefix' => '', ); }
CakePHP のモデルクラスは検索結果を連想配列の配列で返すだけなので、モデルクラス内で table_schema 取得等の検索処理を実装しました。
モデル app/models/datasources/table.php
<?php class Table extends AppModel { // (1) table_schema の一覧取得 function findAllSchemas() { $list = $this->find("all", array( "fields" => "DISTINCT table_schema", "order" => "table_schema" )); return $this->convertResult($list); } // (2) 指定 table_schema に属する table の一覧取得 function findTables($tableSchema) { $list = $this->find("all", array( "conditions" => array( "table_schema" => $tableSchema ), "fields" => "table_name, table_type, engine, avg_row_length, create_time" )); return $this->convertResult($list); } /** * 検索結果を変換する(モデル名をキーにした連想配列の部分を取り除く) * * 例. * <変換前> * array( * array("Table" => array("table_schema" => ・・・)), * array("Table" => array("table_schema" => ・・・)), * ・・・ * ) * * <変換後> * array( * array("table_schema" => ・・・), * array("table_schema" => ・・・), * ・・・ * ) */ private function convertResult($findResult) { //無名関数内から外側スコープの $this にアクセスできないため変数に代入 $outerThis = $this; //無名関数内で $outerThis 変数が使用できるように use 文で指定 return array_map(function($l) use ($outerThis) { return $l[$outerThis->name]; }, $findResult); } }
コントローラーでは検索結果の JSON 化を実施します。
コントローラー app/controllers/informations_controller.php
<?php class InformationsController extends AppController { //使用するモデルの定義 var $uses = "Table"; //View を使わないようにするための設定 var $autoRender = false; // (1) table_schema の一覧取得(JSON化) function databases() { $list = $this->Table->findAllSchemas(); return json_encode($list); } // (2) 指定 table_schema に属する table の一覧取得(JSON化) function tables($tableSchema) { $list = $this->Table->findTables($tableSchema); return json_encode($list); } }
Web ページの実装は以下の通り。Grails のものと同じです。
Webページ app/webroot/index.html
<!DOCTYPE html> <html> ・・・ <script type="text/javascript"> Ext.onReady(function() { //JSONデータの日付フォーマット var dformat = "c"; ・・・ }); </script> ・・・ </html>