Hatena::ブログ(Diary)

TsutomuUchimaの日記 このページをアンテナに追加 RSSフィード

iPhoneでYouTubeの再生が遅い場合の対処はこちらから。

2008-09-11

[][][] Full AjaxCRUD アプリケーション その3  Full Ajax な CRUD アプリケーション その3を含むブックマーク  Full Ajax な CRUD アプリケーション その3のブックマークコメント

前回は画面のモックアップを作成しました。今回は実際にデータベースから取得したデータを表示するよう実装していきます。

データベースのセットアップ

まず最初はデータベースのセットアップを行います。データベースにはデフォルトでEMPテーブルとDEPTテーブルの二つが存在しています。これは必要ないので削除して下さい。

このサンプルで利用する社員テーブルのDDL文を以下に示します。

テーブル名EMPLOYEEのDDL
create table EMPLOYEE (
	ID integer generated by default as identity,
	NAME varchar(255),
	JOB_TYPE varchar(30),
	SALARY integer,
	DEPARTMENT varchar(255)
)

DbLauncherを利用してH2データベースを立ち上げて上記DDL文を実行してください。その後は以下のテストデータを挿入してください。

テストデータ
insert into EMPLOYEE (NAME, JOB_TYPE, SALARY, DEPARTMENT) values ('テストユーザー01', '開発', 200000, '開発部');
insert into EMPLOYEE (NAME, JOB_TYPE, SALARY, DEPARTMENT) values ('テストユーザー02', '開発', 200000, '開発部');
insert into EMPLOYEE (NAME, JOB_TYPE, SALARY, DEPARTMENT) values ('テストユーザー03', '開発', 200000, '開発部');
insert into EMPLOYEE (NAME, JOB_TYPE, SALARY, DEPARTMENT) values ('テストユーザー04', '開発', 200000, '開発部');
insert into EMPLOYEE (NAME, JOB_TYPE, SALARY, DEPARTMENT) values ('テストユーザー05', '開発', 200000, '開発部');
insert into EMPLOYEE (NAME, JOB_TYPE, SALARY, DEPARTMENT) values ('テストユーザー06', '開発', 200000, '開発部');
insert into EMPLOYEE (NAME, JOB_TYPE, SALARY, DEPARTMENT) values ('テストユーザー07', '開発', 200000, '開発部');
insert into EMPLOYEE (NAME, JOB_TYPE, SALARY, DEPARTMENT) values ('テストユーザー08', '開発', 200000, '開発部');
insert into EMPLOYEE (NAME, JOB_TYPE, SALARY, DEPARTMENT) values ('テストユーザー09', '開発', 200000, '開発部');
insert into EMPLOYEE (NAME, JOB_TYPE, SALARY, DEPARTMENT) values ('テストユーザー10', '開発', 200000, '開発部');
insert into EMPLOYEE (NAME, JOB_TYPE, SALARY, DEPARTMENT) values ('テストユーザー11', '開発', 200000, '開発部');
insert into EMPLOYEE (NAME, JOB_TYPE, SALARY, DEPARTMENT) values ('テストユーザー12', '開発', 200000, '開発部');
insert into EMPLOYEE (NAME, JOB_TYPE, SALARY, DEPARTMENT) values ('テストユーザー13', '開発', 200000, '開発部');
insert into EMPLOYEE (NAME, JOB_TYPE, SALARY, DEPARTMENT) values ('テストユーザー14', '開発', 200000, '開発部');
insert into EMPLOYEE (NAME, JOB_TYPE, SALARY, DEPARTMENT) values ('テストユーザー15', '開発', 200000, '開発部');
insert into EMPLOYEE (NAME, JOB_TYPE, SALARY, DEPARTMENT) values ('テストユーザー16', '開発', 200000, '開発部');
insert into EMPLOYEE (NAME, JOB_TYPE, SALARY, DEPARTMENT) values ('テストユーザー17', '開発', 200000, '開発部');
insert into EMPLOYEE (NAME, JOB_TYPE, SALARY, DEPARTMENT) values ('テストユーザー18', '開発', 200000, '開発部');
insert into EMPLOYEE (NAME, JOB_TYPE, SALARY, DEPARTMENT) values ('テストユーザー19', '開発', 200000, '開発部');
insert into EMPLOYEE (NAME, JOB_TYPE, SALARY, DEPARTMENT) values ('テストユーザー20', '開発', 200000, '開発部');
insert into EMPLOYEE (NAME, JOB_TYPE, SALARY, DEPARTMENT) values ('テストユーザー21', '開発', 200000, '開発部');
insert into EMPLOYEE (NAME, JOB_TYPE, SALARY, DEPARTMENT) values ('テストユーザー22', '開発', 200000, '開発部');
insert into EMPLOYEE (NAME, JOB_TYPE, SALARY, DEPARTMENT) values ('テストユーザー23', '開発', 200000, '開発部');
insert into EMPLOYEE (NAME, JOB_TYPE, SALARY, DEPARTMENT) values ('テストユーザー24', '開発', 200000, '開発部');
insert into EMPLOYEE (NAME, JOB_TYPE, SALARY, DEPARTMENT) values ('テストユーザー25', '開発', 200000, '開発部');
insert into EMPLOYEE (NAME, JOB_TYPE, SALARY, DEPARTMENT) values ('テストユーザー26', '開発', 200000, '開発部');
insert into EMPLOYEE (NAME, JOB_TYPE, SALARY, DEPARTMENT) values ('テストユーザー27', '開発', 200000, '開発部');
insert into EMPLOYEE (NAME, JOB_TYPE, SALARY, DEPARTMENT) values ('テストユーザー28', '開発', 200000, '開発部');
insert into EMPLOYEE (NAME, JOB_TYPE, SALARY, DEPARTMENT) values ('テストユーザー29', '開発', 200000, '開発部');
insert into EMPLOYEE (NAME, JOB_TYPE, SALARY, DEPARTMENT) values ('テストユーザー30', '開発', 200000, '開発部');

S2JDBC-Genで各クラスの自動生成

ajax-app/s2jdbc-gen-build.xmlをAnt Buildで実行します。ビルドファイルを右クリックし「Run as」>「Ant Build...」を選択します。ターゲット一覧が表示されますのでその中から「gen-entity」のみを選択し実行します。実行が完了するとEmployee、EmployeeNames、EmployeeCondition、EmployeeService、AbstractService、EmployeeTestの五つ六つクラスが作成されます。

サーバーサイドのデータ一覧取得実装

EmployeeServiceにJSONデータを出力するメソッドfind()を作成します。ソースを以下に示します。

EmployeeService.java
package webapplication.service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import webapplication.dto.PagerConditionDto;
import webapplication.entity.Employee;
import webapplication.entity.EmployeeNames;

/**
 * {@link Employee}のサービスクラスです。
 * 
 * @author tom
 */
public class EmployeeService extends AbstractService<Employee> implements EmployeeNames {

    /**
     * 識別子でエンティティを検索します。
     * 
     * @param id
     *            識別子
     * @return エンティティ
     */
    public Employee findById(Integer id) {
        return select().id(id).getSingleResult();
    }

    /**
     * JSONIC RESTモードで使用。エンティティ一覧を検索します。
     * 
     * @param conditionDto
     * 			ページング条件
     * @return	JSONの為のMapオブジェクト
     */
	public Map<String, Object> find(PagerConditionDto conditionDto) {
		long count = 
			jdbcManager
				.from(Employee.class)
					.getCount();
		List<Employee> list = 			
			jdbcManager
				.from(Employee.class)
					.orderBy(id)
					.offset(conditionDto.start)
					.limit(conditionDto.limit)
					.getResultList();
		Map<String, Object> m = new HashMap<String, Object>();
		m.put("totalProperty", count);
		m.put("root", list);
		return m;
	}

}
PagerConditionDto.java
package webapplication.dto;

public class PagerConditionDto {

	public int start;

	public int limit;

}

ブラウザからhttp://localhost:8080/ajax-app/employee/employee.jsonへアクセスしJSONデータが取得出来るのを確認して下さい。この機能はJSONICサーブレットで実現しています。このサーブレットSeasar2コンポーネントを容易に扱う事が出来て、JSONへの変換も非常に簡単に出来る仕組みになっています。

グリッドのデータソースを実装

Ext.data.Storeを定義しサーバーサイドからデータを取得します。

storeのソース
	var fields = [ 'id', 'name', 'jobType', 'salary', 'department' ];

	var store = new Ext.data.Store({
		proxy: new Ext.data.HttpProxy({
			url: './employee.json',
			method: 'GET'
		}),
		reader: new Ext.data.JsonReader({
		    root: 'root',
		    totalProperty: 'totalProperty',
			fields: fields
		})
	});

	store.load({params:{start:0, limit:pageSize}});

employee.js - storeを定義
Ext.onReady(function(){

	// MODEL, CONTROL

	var fields = [ 'id', 'name', 'jobType', 'salary', 'department' ];

	var store = new Ext.data.Store({
		proxy: new Ext.data.HttpProxy({
			url: './employee.json',
			method: 'GET'
		}),
		reader: new Ext.data.JsonReader({
		    root: 'root',
		    totalProperty: 'totalProperty',
			fields: fields
		})
	});

	// VIEW

	var colModel = new Ext.grid.ColumnModel([
		{header: "Id", width: 75, sortable: true, dataIndex: 'id'},
		{id:'name', header: "氏名", width: 160, sortable: true, dataIndex: 'name'},
		{header: "職種", width: 75, sortable: true, dataIndex: 'jobType'},
		{header: "給与", width: 75, sortable: true, dataIndex: 'salary'},
		{header: "部署", width: 85, sortable: true, dataIndex: 'department'}
	]);

	var addAction = new Ext.Action({
		text: '追加',
		iconCls: 'addIcon'
	});

	var editAction = new Ext.Action({
		text: '編集',
		iconCls: 'editIcon'
	});

	var deleteAction = new Ext.Action({
		text: '削除',
		iconCls: 'deleteIcon'
	});

	var findAction = new Ext.Action({
		text: '検索',
		iconCls: 'findIcon'
	});

	var tbar = [
		addAction,
		'-',
		editAction,
		deleteAction,
		'-',
		findAction
	];

	var pageSize = 20;

	var bbar = new Ext.PagingToolbar({
		id: 'pagingToolbar',
		pageSize: pageSize,
		store: store,
		displayInfo: true,
		displayMsg: '社員の一覧 {2} 件中 {0} - {1} 件目',
		emptyMsg: "社員の一覧はありません"
	});

	var grid = new Ext.grid.GridPanel({
		title:'社員管理',
		stripeRows: true,
		autoExpandColumn: 'name',
		height:523,
		width:600,
		store: store,
		colModel: colModel,
		tbar: tbar,
		bbar: bbar
	});

	// INIT
	
	grid.render('grid-employee');

	store.load({params:{start:0, limit:pageSize}});

});

ブラウザからhttp://localhost:8080/ajax-app/employee/index.htmlを開くとデータが読み込まれて表示されているのが確認できます。

f:id:TsutomuUchima:20080911111252j:image

今回は以上です。サーバーサイドの作成が簡単に出来るのをわかって頂けると思います。

次回はデータの追加、編集、削除機能を実装します。

弟子希望弟子希望 2008/09/18 17:38 こんにちわ、とても有益な記事の掲載を大変感謝します。
ところで、EmployeeService.java内のimport webapplication.dto.PagerConditionDto;
dto.PageConditionDtoはどのように用意するものでしょうか?ヒントだけでもいただければ、ありがたいです。

TsutomuUchimaTsutomuUchima 2008/09/19 08:52 コメントありがとうございます。掲載ミスで抜けていました。追加しておきます。

弟子希望弟子希望 2008/09/19 09:06 Uchima様
おはようございます。ご回答ありがとうございます。
あと、EmployeeService.javaのinsert, update, deleteメソッドがあるバージョンも追加いただければ幸いです。
#最終回で検索機能を実装した完成版での掲載ということだったら、急かせて申し訳ありません。

サーバjson+クライアントjsの実装方法に大変興味がありましたので、今回の記事はとても有益です。
ありがとうございます。

TsutomuUchimaTsutomuUchima 2008/09/19 10:16 コメントありがとうございます。またまた記載ミスです。

まずs2jdbc-gen-build.xmlでgen-entityを実行すると五つのクラスが作成されると記述しましたが実際は六つです。AbstractService.javaが抜けていました。

EmployeeService.javaはAbstractService.javaを継承しています。さらにAbstractService.javaはs2containerのS2AbstractService.javaを継承しています。このS2AbstractService.javaでinsert, update, deleteメソッドが定義されています。EmployeeServiceはこのメソッドを呼び出して実行しています。

S2AbstractService.javaにはこの他にも良く使われるメソッドが定義されていますので一度ソースをご覧になるのをお勧めします。
ブログの記述は後で訂正追記しておきますね。

JSONICを見つけていなかったら私はこの記事を書いていなかったと思います。JSONICすばらしいですよね。

弟子希望弟子希望 2008/09/19 13:17 Uchima様
ご教示ありがとうございます。
AbstractService.javaはチェックしたのですが、その先まではチェックしていませんでした。
アドバイスいただきましたとおり、S2AbstractService.javaのソースをチェックします。
JSONICもS2もすごいのですが、今回の記事のように、実際の使い方の説明のような情報が不足しているように思います。
#S2もPetStoreのようなサンプル実装を用意すればいいのに、と思います。
今回のUchima様の記事はまさに自分にとっては待ち望んでいたものです。次回で最終回とおっしゃらず、続けていただくことを希望します。

初心者H初心者H 2009/06/06 01:12 質問がございます。

EmployeeService.java

public class EmployeeService extends AbstractService<Employee> implements EmployeeNames {
とあるのですが、
eclipseで以下のメッセージが出てしまいます。
型 EmployeeNames は EmployeeService のスーパーインターフェースにできません。スーパーインターフェースはインターフェースでなければなりません

また
public Map<String, Object> find(PagerConditionDto conditionDto) {

.orderBy(id)
についても、eclipseで以下のメッセージが出てしまいます。
id を解決できません。

結果的に
http://localhost:8080/ajax-app/employee/employee.json
にアクセスしても
404エラーとなってしまいます。

大変お手数ですが、解決法を教えていただけましたら幸いです。

TsutomuUchimaTsutomuUchima 2009/06/30 09:55 初心者Hさんはじめまして。

EmployeeService 、 EmployeeNames はS2JDBC-Genを用いて自動生成されるクラスです。何かの拍子でEmployeeNamesに問題が発生しているように伺えます。EmployeeService 、 EmployeeNames 、 Employee クラスを削除し再作成してみて下さい。

taimantaiman 2011/03/25 00:52 はじめまして。ExtJS+Javaの初心者です。

ページング処理の部分で質問がございます。

下記で対象レコードを抽出する方法は分かったのですが、

.offset(conditionDto.start)
.limit(conditionDto.limit)

そもそも、ExtJS側から送られてくるURLパラメータのstartとlimitの値は、
サーバサイドで具体的にどのようにして受け取っているのでしょうか?

以上、ご回答よろしくお願いします。

TsutomuUchimaTsutomuUchima 2011/04/12 15:47 taimanさんはじめまして。

この記事の中程にも書きましたが一覧表示やページング機能はJSONICのRESTサーブレットで実現しています。具体的にどのように受け取っているかは以下のドキュメントを読めば理解出きるかと思います。

http://jsonic.sourceforge.jp/webservice.html#restservlet

このプログラムの例ですとEmployeeServiceがResourceServiceにあたります。そのクラスにfindメソッドを定義し、その引数でPagerConditionDto型のオブジェクトを定義しています。

PagerConditionDtoではstartとlimitを定義しており、JSONICのRESTサーブレットではプロパティ名とURLパラメータの名称が一致していれば値が自動的に入るようになっています。

もっと具体的に知りたければRESTServletのソースを読んでみてください。