Last modified: Mon Jun 13 12:12:15 JST 2005 since 2005/03/30
このレポートは、ショッピングカートの実装を通じてオープンソースツールをどのように活用するかを検証するレポートのDB編である。
UNIX時代には「いかにしてツールを使うか」がプログラミングセンスの見せ所であった。言語もCからjavaに移って、開発環境もGUIペースのEclipseに取って替わられた感がある今日この頃だが、このような状況でどのようにしてソフトウェアを開発すればよいのか、オープンソースのツールを使いながら紹介する。
JDBCプログラミングにすぐ効くDbUtils活用マニュアルに紹介されたDbUtilsは、検索後のデータマッピングに優れており、サイズも小さいので小さいシステムでのDB構築に向いていると思われる。しかしながらERマッピングツール(1)と比べるとデータの挿入や更新処理に対して個別にAPIを使ったコーディングが必要である。EDbHelperとEDBUtilsというユーティリティクラスを使って、DbUtilsをもう少し使いやすくしてみる。(2)
最初に単純な例でEDbUtilsがどれくらい簡単にDBにアクセスできるかを例を使って説明する。MemberというBeanクラスが以下のような属性を持っていた場合、
対応するテーブルは、
IDENTITY | ID |
VARCHAR | ADDRESS |
VARCHAR | NAME |
となる。Memberクラスのjavaソースは、
package jp.co.pwv.estore.business; /** * Member * * @author Hiroshi TAKEMOTO */ public class Member { private Integer id; private String name; private String address; public Member() { this(null, null); } public Member(String name, String address) { this.name = name; this.address = address; } public String getAddress() { return address; } public Integer getId() { return id; } public String getName() { return name; } public void setAddress(String string) { address = string; } public void setId(int i) { id = new Integer(i); } public void setName(String string) { name = string; } public void setId(Integer integer) { id = integer; } }
のようになり、id, name, addressの属性を定義した後、Eclipseのgetter, setterの生成を行った。Sample1のインスタン生成を簡単にするために、Member(String name, String address)のコンストラクタも用意した。
サンプルプログラムSample1.javaのソースは、以下の通りである。
package examples; import java.util.List; import javax.sql.DataSource; import jp.co.pwv.estore.business.Member; import jp.co.pwv.estore.util.EDbUtils; /** * Sample1 * * @version 1.0 2004/09/03 * @author Hiroshi TAKEMOTO */ public class Sample1 { public static void main(String[] args) { // EDbUtilsを使う前処理 Object[] objs = {new Member()}; DataSource ds = EDbUtils.createDatasource( "org.hsqldb.jdbcDriver", "jdbc:hsqldb:hsql://localhost", "sa", ""); EDbUtils.createHelperMap(objs); // テーブルの作成 EDbUtils.createTables(); // Memberのインスタンをテーブルに挿入する例 Member user1 = new Member("竹本", "東京都中野区"); EDbUtils.insertObject(user1); // DBからIDを指定してオブジェクトをロードする例 // user1と同じIDのオブジェクトをロードする Member user2 = (Member)EDbUtils.loadObject(Member.class, user1.getId()); System.out.println(EDbUtils.toString(user2)); // テーブルからすべてのオブジェクトを取りだす例 List list = EDbUtils.selectObjects(Member.class, ""); for (int i = 0; i < list.size(); i++) { Member member = (Member)list.get(i); System.out.println(EDbUtils.toString(member)); } // 使用済みのテーブルを削除(通常は不要) // EDbUtils.dropTable(Member.class); } }
動作試験の前に、使用するデータベースHSQLDBをサーバモードで起動する必要がある。(3)今回は、HSQLDB 1.7.1のdemoにあるrunServer.batを使ってDBサーバを起動する。
> runServer.bat
次にサンプルプログラムを実行するが、EDbUtilsは以下のライブラリを使用するため
最後のhsqldb.jarは、使用するデータベースによって変更する必要がある。Sample1を実行すると以下の結果がコンソールに出力される。
MEMBER: ADDRESS=東京都中野区 ID=0 NAME=竹本 MEMBER: ADDRESS=東京都中野区 ID=0 NAME=竹本
DBの内容が正しく登録されているかは、HSQLDB 1.7.1のdemoにあるrunManager.batを使って調べることができる。
左のパネルにT_MEMBERが追加され、その内容がID, ADDRESS, NAMEとなっており、右のパネルでselect * from t_member;とSQL文を入力し、Executeボタンを押すと
ID | ADDRESS | NAME |
---|---|---|
0 | 東京都中野区 | 竹本 |
と表示される。では、Sample1.javaの処理をブロック毎に説明する。前処理のブロックでは、
の処理を行う。
// EDbUtilsを使う前処理 Object[] objs = {new Member()}; DataSource ds = EDbUtils.createDatasource( "org.hsqldb.jdbcDriver", "jdbc:hsqldb:hsql://localhost", "sa", ""); EDbUtils.createHelperMap(objs); // テーブルの作成 EDbUtils.createTables();
次にオブジェクトをDBのテーブルに挿入する処理のブロックについて説明する
// Memberのインスタンをテーブルに挿入する例 Member user1 = new Member("竹本", "東京都中野区"); EDbUtils.insertObject(user1);
DBからオブジェクトを取り出すには、idに対応するオブジェクト(5)を読み込むEDbUtils.loadObjectと複数のオブジェクトを取り出すEDbUtils.selectObjectsがある。EDbUtils.loadObjectの処理は、次のようになる。
// DBからIDを指定してオブジェクトをロードする例 // user1と同じIDのオブジェクトをロードする Member user2 = (Member)EDbUtils.loadObject(Member.class, user1.getId()); System.out.println(EDbUtils.toString(user2));
同様にEDbUtils.selectObjectsは、次のようになる。
// テーブルからすべてのオブジェクトを取りだす例 List list = EDbUtils.selectObjects(Member.class, ""); for (int i = 0; i < list.size(); i++) { Member member = (Member)list.get(i); System.out.println(EDbUtils.toString(member)); }
EDbUtilsでは、テーブルの作成、レコードの挿入、レコードの更新、レコードの削除等DbUtilsに含まれていない機能をヘルパークラスEDbHelperを内部で使って処理している。EDbHelperは、javaのリフレクションの機能を使ってBeanの属性とその型に応じたSQL文を生成する。Sample1で使ったMemberクラスを例にどのようなSQL文が生成されるか示す。Eclipseのスクラップを使って以下のプログラム断片を実行すると
Member member = new Member(); member.setId(10); member.setName("Hiroshi TAKEMOTO"); member.setAddress("中野区本町"); EDbHelper helper = new EDbHelper(member); System.out.println(helper.getCreateTableStmt()); System.out.println(helper.getInsertStmt(member)); System.out.println(helper.getUpdateStmt(member)); System.out.println(helper.getDeleteStmt(member));
以下のような出力を得る。
CREATE TABLE T_MEMBER(ID IDENTITY NOT NULL PRIMARY KEY,ADDRESS VARCHAR,NAME VARCHAR) INSERT INTO T_MEMBER (ID,ADDRESS,NAME)VALUES(NULL,'中野区本町','Hiroshi TAKEMOTO') UPDATE T_MEMBER SET ADDRESS='中野区本町',NAME='Hiroshi TAKEMOTO' WHERE ID=0 DELETE FROM T_MEMBER WHERE ID=0
テーブル名は、クラス名の前にT_が付き、クラスの属性と対応するSQL型を持ち、
のように各SQL文が組み立てられる。(6)
オブジェクト間の関係をDBのテーブルにマッピングする場合を例に取ってみる。例として、本(Book)とその著者(People)の関係を1:1対応(7)について考えてみる。クラスの関係は、
のようにBookから著者authorを参照している。DBでは、参照するauthorオブジェクトの識別子をauhtorRefで関係付けするものとする。そのような場合、データのロードや挿入時にこれらの関係を処理する必要が出てくる。この処理をするインタフェースがIEPropertiesである。IEPropertiesインタフェースでは、
を実装する必要がある。
BookのinsertPropertiesは、つぎのようにものになる。
public void insertProperties() { if (author != null) { EDbUtils.insertObject(author); authorRef = author.getId(); } }
著者への参照オブジェクトauthorがNULLで無かったら、authorをDBに挿入し、その識別子をauthorRefにセットする。
insertPropertiesとは、逆にloadPropertiesでは、Peopleテーブルへの参照IDであるauthorRefがNULLでない場合に、Peopleテーブルから著者authoreをloadObjectを使ってロードし、セットする。
public void loadProperties() { if (authorRef != null) author = (People)EDbUtils.loadObject(People.class, authorRef); }
printPropertiesは、DBとは直接関係はないが、デバッグ時にオブジェクトの内容を出力するときに使用する。不要の場合には、空のメソッドを定義すればよい。Bookでは、"author->(著者オブジェクトの内容)"のような形式で出力している。
public String printProperties() { if (author != null) return (" author->(" + EDbUtils.toString(author) + ")"); else return (" author->NULL"); }
次にBook, PeopleのサンプルプログラムSample2.javaを示す。
// Bookのインスタンをテーブルに挿入する例 EDbUtils.insertObject(book);
の部分でbookオブジェクトをDBに挿入するとその参照オブジェクトであるauthorも同時にDBに挿入されている点に注意されたい。
package examples; import javax.sql.DataSource; import jp.co.pwv.estore.business.Book; import jp.co.pwv.estore.business.People; import jp.co.pwv.estore.util.EDbUtils; /** * Sample1 * * @version 1.0 2004/09/03 * @author Hiroshi TAKEMOTO */ public class Sample2 { public static void main(String[] args) { // テストデータを作成 Book book = new Book(); book.setTitle("1:1の例題"); People author = new People(); author.setName("Hiroshi TAKEMOTO"); book.setAuthor(author); // EDbUtilsを使う前処理 Object[] objs = {book, author}; DataSource ds = EDbUtils.createDatasource( "org.hsqldb.jdbcDriver", "jdbc:hsqldb:hsql://localhost", "sa", ""); EDbUtils.createHelperMap(objs); // テーブルの作成 EDbUtils.createTables(); // Bookのインスタンをテーブルに挿入する例 EDbUtils.insertObject(book); // DBからIDを指定してオブジェクトをロードする例 // user1と同じIDのオブジェクトをロードする Book ret = (Book)EDbUtils.loadObject(Book.class, book.getId()); System.out.println(EDbUtils.toString(ret)); // 使用済みのテーブルを削除(通常は不要) EDbUtils.dropTable(Book.class); EDbUtils.dropTable(People.class); } }
もう一つ線形リストにIEPropertiesを使った例を示す。線形リストLinkedListには、id, value, next, nextRefが属性として定義され、Bookと同様にIEPropertiesのメソッドを実装したのが、LinkedList.javaである。
package examples; import jp.co.pwv.estore.util.EDbUtils; import jp.co.pwv.estore.util.IEProperties; /** * LinkedList * * @author Hiroshi TAKEMOTO */ public class LinkedList implements IEProperties { private Integer id; private String value; private Integer nextRef; private LinkedList next; public LinkedList() { this(null); } public LinkedList(String value) { this.value = value; } public void add(LinkedList obj) { this.next = obj; } public void insertProperties() { if (next != null) { EDbUtils.insertObject(next); nextRef = next.getId(); } } public void loadProperties() { if (nextRef != null) next = (LinkedList)EDbUtils.loadObject(LinkedList.class, nextRef); } public String printProperties() { if (next != null) return (" next->(" + EDbUtils.toString(next) + ")"); else return (" next->NULL"); } // Eclipseで自動生成されたgetter, setter public Integer getId() { return id; } public String getValue() { return value; } public LinkedList getNext() { return next; } public Integer getNextRef() { return nextRef; } public void setId(Integer integer) { id = integer; } public void setValue(String string) { value = string; } public void setNext(LinkedList list) { next = list; } public void setNextRef(Integer integer) { nextRef = integer; } }
Sample2と同様に線形リストのトップの要素l1に対してのみEDbUtils.insertObject(l1)を実行するだけで、リスト上のすべての要素がDBに挿入され、トップの要素のに対するEDbUtils.loadObject(LinkedList.class, l1.getId())で線形リストの構造を完全に復元することができる。Sample3の実行結果は、次のようになる。(8)
LINKEDLIST: ID=2 NEXT=examples.LinkedList@1ac2f9c NEXTREF=1 VALUE=a next->(LINKEDLIST: ID=1 NEXT=examples.LinkedList@169ca65 NEXTREF=0 VALUE=b next->(LINKEDLIST: ID=0 VALUE=c next->NULL))
すこし見にくいがl1->l2->l3のリンクが正常に復元されているのが分かる。Sample3のソースは、以下の通りである。
package examples; import javax.sql.DataSource; import org.hsqldb.jdbcDriver; import jp.co.pwv.estore.util.EDbUtils; import jp.co.pwv.estore.util.IEProperties; /** * Sample1 * * @version 1.0 2004/09/03 * @author Hiroshi TAKEMOTO */ public class Sample3 { public static void main(String[] args) { // テストデータを作成 LinkedList l1 = new LinkedList("a"); LinkedList l2 = new LinkedList("b"); LinkedList l3 = new LinkedList("c"); // l1->l2->l3の単方向のリストを作成 l1.add(l2); l2.add(l3); // EDbUtilsを使う前処理 Object[] objs = {l1}; DataSource ds = EDbUtils.createDatasource( "org.hsqldb.jdbcDriver", "jdbc:hsqldb:hsql://localhost", "sa", ""); EDbUtils.createHelperMap(objs); // テーブルの作成 EDbUtils.createTables(); // Bookのインスタンをテーブルに挿入する例 EDbUtils.insertObject(l1); // l1と同じIDのオブジェクトをロードする LinkedList ret = (LinkedList)EDbUtils.loadObject(LinkedList.class, l1.getId()); System.out.println(EDbUtils.toString(ret)); // 使用済みのテーブルを削除(通常は不要) EDbUtils.dropTable(LinkedList.class); } }
これまでの説明で、EDbUtilsを使うことによってオブジェクトとDBの連携がきわめてスムーズに行われているような印象を得るだろう。これにはいくつかのトリックがある。
EDbUtils, EDbHelperは、コメントを入れても1000行に満たない小さなユーティリティであるが、いくつかの規約と制限を享受することによって、DbUtilsというツールを有効に利用することが可能になる。また、説明が抜けていたが、volatile宣言された属性は、テーブルのフィールドへのマッピングは行われないので、この機能を利用すれば、不要なフィールドのDBへの投入を防ぐことができる。
オブジェクトの更新と削除について、EDbUtilsのjunit用単体テストコードを例に説明する。
オブジェクトの更新は、きわめて簡単で変更したオブジェクトに対して、EDbUtils.updateObject(変更したオブジェクト)を実行するだけである。testUpdateでは、Memberのaddressが"addr a"から"addr AAA"に更新され、EDbUtils.loadObjectでロードされたretと等しいことが確認できる。
public void testUpdate() { Member user1 = new Member("a", "addr a"); EDbUtils.insertObject(user1); user1.setAddress("addr AAA"); EDbUtils.updateObject(user1); System.out.println(EDbUtils.toString(user1)); Member ret = (Member)EDbUtils.loadObject(Member.class, user1.getId()); assertEquals(EDbUtils.toString(user1), EDbUtils.toString(ret)); }
オブジェクトの削除は、もっと簡単である。EDbUtils.deleteObjectに削除したいオブジェクトを渡すだけである。
EDbUtils.deleteObject(user2);
DbUtilsのドキュメントには、コネクションを接続してからの処理に対し、トランザクション処理を行うことができる旨の説明がある。これには、データベースのAUTOCOMMITをFALSEにする必要がある。EDbUtilsでは、loadObject, selectObject, updateObject, deleteObjectにコネクションとヘルパーマップを先頭に追加した引数を持つメソッドを提供し、トランザクション処理を実現できるようにしている。junitの単体テストメソッドtestTransactionを使ってトランザクション処理を説明する。
public void testTransaction() { // Block 1:テーブルにuser1のデータ1個のみが存在する状態 Member user1 = new Member("a", "addr a"); EDbUtils.insertObject(user1); List list = EDbUtils.selectObjects(Member.class, ""); assertEquals(1, list.size()); try { // Block 2:ヘルパーマップ、コネクションを取得し、user2, user3を挿入する HashMap map = EDbUtils.getMap(); Connection conn = ds.getConnection(); // AUTOCOMMIT FALSEにする EDbUtils.setTransaction(conn); Member user2 = new Member("b", "addr b"); EDbUtils.insertObject(conn, map, user2); Member user3 = new Member("c", "addr c"); EDbUtils.insertObject(conn, map, user3); // Block 3:user1と同じデータをロードし、nameを変更後、削除する Member ret = (Member)EDbUtils.loadObject(Member.class, user1.getId()); ret.setName("Takemoto"); EDbUtils.updateObject(conn, map, ret); EDbUtils.deleteObject(conn, map, ret); // Block 4:user1が削除され、テーブルの総数が2レコードであることを確認 list = EDbUtils.selectObjects(conn, Member.class, ""); assertEquals(2, list.size()); // Block 5:ロールバックを実施 DbUtils.rollback(conn); DbUtils.closeQuietly(conn); // Block 6:ロールバック後のDBにあるデータがuser1のみであることを確認 conn = ds.getConnection(); list = EDbUtils.selectObjects(conn, Member.class, ""); assertEquals(1, list.size()); assertEquals(EDbUtils.toString(user1), EDbUtils.toString(list.get(0))); } catch (Exception e) { fail(); } }
コンポジットパターンのようなツリー構造をデータベースにマッピングするためにEDbUtilsを拡張し、以下のようなノードマップテーブルを導入し、複数のクラスのインスタンスからなるリストを扱えるユーティリティメソッドを追加した。(10)
カラム名 | 型 | 内容 |
---|---|---|
ID | INTEGER | レコード識別子 |
OWNERCLSNAME | VARCHAR | 保有者のクラス名 |
OWNERREF | INTEGER | 保有者が割り当てたID |
TARGETCLSNAME | VARCHAR | 実際のオブジェクトのクラス名 |
TARGETREF | INTEGER | オブジェクトのレコードの識別子 |
追加したメソッドは以下の通りである。(11)
メソッド名 | 内容 |
---|---|
insertMappedList | リストをENODEMAPPERにマップし、DBに挿入する |
loadMappedList | 識別子ownerIdを持つオーナーオブジェクトのENODEMAPPERにマップされたリストをロードする |
loadMappedObject | ENODEMAPPERにマップされたオブジェクトをロードする |
insertMappedObject | オブジェクトをENODEMAPPERにマップし、DBに挿入する |
relaxerは、RELAXで記述されたXMLをjavaのクラスにマッピングするツールであり、XMLとjavaのオブジェクトマッピングの他に、コンポジットパターン、ビジターパターンを扱う構造への変換をサポートしている。今回はrelaxerのビジターパターンを使って、relaxerによって自動生成されたexample.relaxer.menuパッケージのMenubar, Menu, Itemからそのサブクラスexmpale.relaxer.emenuに変換し、XMLファイルのツリー構造をデータベースに挿入とデータベースからの読み込みを行う例を示す。パッケージexample.relaxer.menu, example.relaxer.emenuのクラス関連図を以下に示す。
Menubarは、複数のMenuから構成され、Menuには、MenuまたはItemが含まれるツリー構造を持っている。relaxerのRELAXソースmenu.rlxは、参考文献のメニューサンプルから引用した。
<?xml version="1.0"?> <!DOCTYPE module PUBLIC "-//RELAX//DTD RELAX Core 1.0//JA" "relaxCore.dtd"> <module moduleVersion="1.0" relaxCoreVersion="1.0"> <interface> <export label="menubar"/> </interface> <elementRule label="menubar"> <tag> </tag> <sequence> <ref label="menu" occurs="*"/> </sequence> </elementRule> <elementRule label="menu"> <tag> </tag> <sequence> <ref label="title"/> <choice occurs="*"> <ref label="item"/> <ref label="menu"/> </choice> </sequence> </elementRule> <elementRule label="title" type="string"> <tag/> </elementRule> <elementRule label="item" type="string"> <tag> </tag> </elementRule> </module>
RELAXソースからjavaのソースに変換するrelaxerの起動は、以下の通りです。
relaxer -visitor -package:examples.relaxer.menu menu.rlx
relaxerの生成したソースを加工してDBへのアクセス処理を追加することも可能であるが、それでは仕様変更に弱い実装になる。そこでrelaxerの持つビジターパターンを使ってMenubar, Menu, Itemのツリー構造をEMenubar, EMenu, EItemのツリー構造に変換する。変換するビジターEMenuVisitorのソースを以下に示す。
package examples.relaxer.emenu; import java.util.Stack; import examples.relaxer.menu.*; /** * @version menu.rxm 1.0 (Sun Sep 12 21:54:00 JST 2004) * @author Relaxer 1.0rc2 (http://www.relaxer.org) */ public class EMenuVisitor extends RVisitorBase { EMenubar menubar = null; Stack stack = new Stack(); /** * Visits this node for enter behavior. * * @param visitable * @return boolean */ public boolean enter(Menubar visitable) { menubar = new EMenubar(visitable); menubar.clearMenu(); stack.push(menubar); return (true); } /** * Visits this node for leave behavior. * * @param visitable */ public void leave(Menubar visitable) { stack.pop(); } /** * Visits this node for enter behavior. * * @param visitable * @return boolean */ public boolean enter(Menu visitable) { EMenu menu = new EMenu(visitable); menu.clearContent(); IRNode parent = (IRNode)visitable.rGetParentRNode(); if (parent instanceof Menubar) { EMenubar menubar = (EMenubar)stack.peek(); menubar.addMenu(menu); } else if (parent instanceof Menu) { EMenu menuItem = (EMenu)stack.peek(); menuItem.addContent(menu); } stack.push(menu); return (true); } /** * Visits this node for leave behavior. * * @param visitable */ public void leave(Menu visitable) { stack.pop(); } /** * Visits this node for enter behavior. * * @param visitable * @return boolean */ public boolean enter(Item visitable) { EItem item = new EItem(visitable); IRNode parent = (IRNode)visitable.rGetParentRNode(); if (parent instanceof Menu) { EMenu menuItem = (EMenu)stack.peek(); menuItem.addContent(item); } stack.push(item); return (true); } /** * Visits this node for leave behavior. * * @param visitable */ public void leave(Item visitable) { stack.pop(); } /** * @return */ public EMenubar getMenubar() { return menubar; } }
ビジターは、relaxerが生成したRVisitorBase.javaをコピー&ペーストして作成した。ビジターの処理は、きわめて単純であり、
を実装すればよい。
順序は逆になったが、パッケージexample.relaxer.emenuのクラスを実装する。独自クラスで行う処理は、きわめて簡単であり、
だけである。EMenu.javaを例にinsertPropertiesの処理を見る。基本的には、コンポジットがあれば、getNewOwnerIdメソッドで新しい所有者IDを取得し、それを元にinsertMappedListメソッドを呼び出すだけである。
// DBで参照関係確認のために挿入 if (rGetParentRNode() != null) { int parentId = EDbUtils.insertMappedObject(this, EDbUtils.getNewOwnerId(), rGetParentRNode()); parentRNodeRef = new Integer(parentId); } // メニューがコンテンツを持っている場合 if (getContent().length > 0) { contentOwnerId = EDbUtils.getNewOwnerId(); EDbUtils.insertMappedList(this, contentOwnerId, Arrays.asList(getContent())); }
同様にloadPropertiesは、contentOwnerIdがnullで無い場合、loadMappedListメソッドでコンポジットのリストを取得し、コンポジットに追加するだけである。(13)
// メニューがコンテンツを持っていて、まだDBから読み込まれていない場合 if (contentOwnerId != null && getContent().length == 0) { List list = EDbUtils.loadMappedList(this, contentOwnerId); if (list != null) { for (int i = 0; i < list.size(); i++) { addContent((IMenuChoice)list.get(i)); } } }
EMenubar, EMenu, EItemの完全なソースを以下に示す。
package examples.relaxer.emenu; import java.util.Arrays; import java.util.List; import jp.co.pwv.estore.util.*; import examples.relaxer.menu.*; /** * EMenubar * @version 1.0 2004/09/08 * @author Hiroshi TAKEMOTO */ public class EMenubar extends Menubar implements IEProperties { private Integer id; private Integer parentRNodeRef; private Integer menuOwnerId; public EMenubar() { super(); } public EMenubar(Menubar menubar) { super(menubar); } public void insertProperties() { // DBで参照関係確認のために挿入 if (rGetParentRNode() != null) { int parentId = EDbUtils.insertMappedObject(this, EDbUtils.getNewOwnerId(), rGetParentRNode()); parentRNodeRef = new Integer(parentId); } // メニューを持っている場合 if (getMenu().length > 0) { // List<EMenu>なので menuOwnerId = EDbUtils.getNewOwnerId(); EDbUtils.insertMappedList(this, menuOwnerId, Arrays.asList(getMenu())); } } public void loadProperties() { // メニューバーがメニューを持っていて、まだロードされていない場合 if (menuOwnerId != null && getMenu().length == 0) { // List<EMenu>なので List list = EDbUtils.loadMappedList(this, menuOwnerId); if (list != null) { for (int i = 0; i < list.size(); i++) { addMenu((Menu)list.get(i)); } } } } public String printProperties() { StringBuffer buf = new StringBuffer(); if (parentRNodeRef != null) { buf.append(" parentRNodeRef = " + parentRNodeRef); } else { buf.append(" menuOwnerId = NULL"); } if (menuOwnerId != null) { buf.append(" menuOwnerId = " + menuOwnerId); } else { buf.append(" menuOwnerId = NULL"); } return (buf.toString()); } public String toString() { return (super.toString()); } // Eclipseで自動生成されたgetter, setter public Integer getId() { return id; } public Integer getParentRNodeRef() { return parentRNodeRef; } public void setId(Integer integer) { id = integer; } public void setParentRNodeRef(Integer integer) { parentRNodeRef = integer; } public Integer getMenuOwnerId() { return menuOwnerId; } public void setMenuOwnerId(Integer integer) { menuOwnerId = integer; } }
package examples.relaxer.emenu; import java.util.Arrays; import java.util.List; import jp.co.pwv.estore.util.*; import examples.relaxer.menu.*; /** * EMenu * @version 1.0 2004/09/08 * @author Hiroshi TAKEMOTO */ public class EMenu extends Menu implements IEProperties { private Integer id; private Integer parentRNodeRef; private Integer contentOwnerId; public EMenu() { super(); } public EMenu(Menu menu) { super(menu); } public void insertProperties() { // DBで参照関係確認のために挿入 if (rGetParentRNode() != null) { int parentId = EDbUtils.insertMappedObject(this, EDbUtils.getNewOwnerId(), rGetParentRNode()); parentRNodeRef = new Integer(parentId); } // メニューがコンテンツを持っている場合 if (getContent().length > 0) { contentOwnerId = EDbUtils.getNewOwnerId(); EDbUtils.insertMappedList(this, contentOwnerId, Arrays.asList(getContent())); } } public void loadProperties() { // メニューがコンテンツを持っていて、まだDBから読み込まれていない場合 if (contentOwnerId != null && getContent().length == 0) { List list = EDbUtils.loadMappedList(this, contentOwnerId); if (list != null) { for (int i = 0; i < list.size(); i++) { addContent((IMenuChoice)list.get(i)); } } } } public String printProperties() { StringBuffer buf = new StringBuffer(); if (parentRNodeRef != null) { buf.append(" parentRNodeRef = " + parentRNodeRef); } else { buf.append(" menuOwnerId = NULL"); } if (contentOwnerId != null) { buf.append(" contentOwnerId = " + contentOwnerId); } else { buf.append(" contentOwnerId = NULL"); } return (buf.toString()); } public String toString() { return (super.toString()); } // Eclipseで自動生成されたgetter, setter public Integer getContentOwnerId() { return contentOwnerId; } public Integer getId() { return id; } public Integer getParentRNodeRef() { return parentRNodeRef; } public void setContentOwnerId(Integer integer) { contentOwnerId = integer; } public void setId(Integer integer) { id = integer; } public void setParentRNodeRef(Integer integer) { parentRNodeRef = integer; } }
package examples.relaxer.emenu; import jp.co.pwv.estore.util.*; import examples.relaxer.menu.*; /** * EItem * @version 1.0 2004/09/08 * @author Hiroshi TAKEMOTO */ public class EItem extends Item implements IEProperties { private Integer id; private Integer parentRNodeRef; public EItem() { super(); } public EItem(Item item) { super(item); } public void insertProperties() { // DBで参照関係確認のために挿入 if (rGetParentRNode() != null) { int parentId = EDbUtils.insertMappedObject(this, EDbUtils.getNewOwnerId(), rGetParentRNode()); parentRNodeRef = new Integer(parentId); } } public void loadProperties() { } public String printProperties() { StringBuffer buf = new StringBuffer(); if (parentRNodeRef != null) { buf.append(" parentRNodeRef = " + parentRNodeRef); } else { buf.append(" menuOwnerId = NULL"); } return (buf.toString()); } public String toString() { return (super.toString()); } // Eclipseで自動生成されたgetter, setter public Integer getId() { return id; } public Integer getParentRNodeRef() { return parentRNodeRef; } public void setId(Integer integer) { id = integer; } public void setParentRNodeRef(Integer integer) { parentRNodeRef = integer; } }
最後に、XMLからメニュー情報を読み込みDBに投入するテストプログラムを作成する。Sample4.javaがテストプログラムである。
package examples; import javax.sql.DataSource; import jp.co.pwv.estore.util.*; import examples.relaxer.menu.*; import examples.relaxer.emenu.*; /** * Sample1 * * @version 1.0 2004/09/03 * @author Hiroshi TAKEMOTO */ public class Sample4 { public static void main(String[] args) { // EDbUtilsを使う前処理 Object[] objs = {new ENodeMapper(), new EMenubar(), new EMenu(), new EItem() }; DataSource ds = EDbUtils.createDatasource( "org.hsqldb.jdbcDriver", "jdbc:hsqldb:hsql://localhost", "sa", ""); EDbUtils.createHelperMap(objs); // 既存テーブルを削除(通常は不要) EDbUtils.dropTables(); // テーブルの作成 EDbUtils.createTables(); // String uri = args[0]; try { Menubar orgMenubar = new Menubar(uri); EMenuVisitor visitor = new EMenuVisitor(); URVisitor.traverse(orgMenubar, visitor); EMenubar menubar = visitor.getMenubar(); // EMenubarのインスタンをテーブルに挿入する例 EDbUtils.insertObject(menubar); //menubarと同じIDのオブジェクトをロードする EMenubar ret = (EMenubar)EDbUtils.loadObject(EMenubar.class, menubar.getId()); System.out.println(EDbUtils.toString(ret)); } catch (Exception e){ System.err.println(e.toString()); } } }
テストデータを以下に示す。
<?xml version="1.0" encoding="Shift_JIS"?> <!DOCTYPE menubar SYSTEM "menu.dtd"> <menubar> <menu> <title>編集</title> <item>新規</item> <item>開く</item> <item>閉じる</item> <item>終了</item> </menu> <menu> <title>セットアップ</title> <menu> <title>ディスプレイ</title> <item>カラー</item> <item>フォント</item> </menu> <item>WWW</item> </menu> <menu> <title>ヘルプ</title> <item>Sample4</item> </menu> </menubar>
[3] | 加納 隆征; 羽生 彰洋. はじめてのHSQLDB : コンパクトながらタフなオープンソースRDBMS. WEB+DB p.139-149 17.技術評論社, 2003 |
[4] | 山根 健裕. JDBCプログラミングにすぐ効くDbUtils活用マニュアル : コンパクトでシンプルな超便利ライブラリ. WEB+DB p.66-75 20.技術評論社, 2004 |
[5] | 浅海 智晴. Relaxer : Java/XMLによるWeb開発. ピアソン・エデュケーション, |