top / index / prev / next / target / source
日記形式でつづる いがぴょんコラム ウェブページです。
いま世間的に望まれているのは RDBに近いレイヤーでのソースコード生成タイプのO-RマッピングならぬR-Oマッピングであるように思えます。
ここのところ 有識者の方々のWeb日記を見ていると、どうもみなさん 納得のいく O-Rマッピングツールを探して いろいろなオープンソースプロダクトを物色しまくられているようです。EJBは重すぎて、しかし世間の 『純粋な』O-Rマッピングの各種ツールはどうもなじめない。かといって RDBに近い種類 、分類するならば R-Oマッピングとでも表現できるようなツールはまだ現状世間的には一長一短である。、、、で 実は 私自身 私が完全にフィットして納得する O-Rマッピングツールが無くて 不満を感じている人の一人です。
賛同される方はいらっしゃいますか?
納得行くプロダクトが既に存在はしていないのであれば、それじゃ新規に作ってしまえ、という乱暴な発想をしてみました。ええ、私は良くそういうことを思いつきます。特に オブジェクトとRDBとのマッピングには いつも気にしていて、そしてこだわりがある方なので、そこばかりは自作したって良いのではないかと考えたのです。(まえ一回自作した経験も持っていますし…)
ということで、賛同者の存在はさておき、下記のようなスペックのR/Oマッピングツールを構想中です。有識者のみなさんとりあえず興味を感じてもらえますか?
ポリシー
RDBを中心とした従来型 オブジェクト-RDBマッピングについて、ソースコード自動生成を実現する。
最小限の設定ファイル指定にてR-Oマッピングが動作するようにする。
拡張機能として、より詳しい設定ファイル指定により、より高機能な (RDBで良く使いがちな)検索機能などが指定できるようになる。
仕様案
RDBを中心とした R-Oマッピングツール。すなわち RDBにJavaのオブジェクトの型を合わせる。
RDB上のテーブルのスキーマを入力として Javaクラスファイルを生成する。
基本的には RDBのテーブルひとつづつに対応したJavaのクラスを作成する。
基本的には RDBのテーブル一つずつに対応した Javaのクラスを生成する (1テーブル = 1クラス)。INSERT文やUPDATE文、DELETE文は対象テーブルに対して動作する。 (2004.01.23 追記 ただし 参照系=SELECTは複数テーブル結合が利便性が高いので この点を拡張機能による設定ファイルでの指定が可能なようにする)
このクラスを拡張したい場合には 原則として継承してメソッドを追加するようにする。自動生成されたソースファイルは変更を加えない運用を想定する。
ソースコード生成タイプとする。特定のライブラリには依存せず、単体でコンパイルおよび動作することが可能なJavaクラスファイルを生成する。
ライセンスは LGPLとする。(ここはこだわる方も多いのかも知れませんが…) ご希望のライセンスなど有りましたらツッコミ下さい。 既に存在するRDBのテーブルを指定して R-Oマッピングツールを起動する
シンプルモードでは、JDBCデータソース、ユーザ名、パスワードのみの指定。
拡張モードでは、シンプルモードの設定に加え、追加の検索やテーブルJOIN などを設定ファイルで設定する。
↓
指定されたテーブルに対するアクセサクラスのソースファイルが生成される。
Tbl<テーブル名>.java
用意するメソッドは下記の通り
ArrayList static selectAll(Connection conn) 全件SELECTを行うメソッド
<このクラス自身の型> select(Connection conn, String primarykey) プライマリーキーを条件に1レコード取得するメソッド
boolean insert(Connection conn) 1レコード追加する
boolean update(Connection conn) プライマリーキーを条件に1レコード更新する
boolean delete(Connection conn) プライマリーキーを条件に1レコード削除する
パラメータをセットするための内部的に生成されるヘルパーメソッド
↓
特定のライブラリに依存せずに Java APIと共にコンパイルすることが可能であるようにする。
↓
特定のライブラリに依存せずに 実行可能であるようにする。
実動としては こんなイメージ
『社員』テーブルがあったとする。社員コード、名前、年齢、性別といったスキーマを持っているとする。
ユーザ名・パスワード・表名『社員』 を指定して R-Oマッピングソースコード生成ツールを起動する
クラス 『社員』が作成される。privateフィールドとして 社員コード、名前、年齢、性別 が生成され、おのおの セッター・ゲッターが作成される。
テーブルに対するアクセサメソッドが作成されて提供される。
う~ん。日本語と英語のマッピングに関する考慮は やはり必要だろうか… しかしいっそのこと、テーブルが日本語名だったらクラス名も日本語で良いかも…。
ここで大事なのは、おそらく RDB上の型とJavaの型とのマッピングをどのようにつけるのか、についてです。う~む この点も非常に興味深い。、、、ってな感じで 現在 仕様を抽出中です。前回作成した R-Oマッピングツールでの反省も生かしたいので、今回はじっくりと仕様を考え中です。
2004.01.22 追記 R-O Map をイメージするための試作ソフト (モックアップ) を作成してみました。あるユーザでログインして利用可能な実テーブルに対するアクセサDAOのJavaソースコードをずんどこ生成します。実行はこれ専用のディレクトリで解凍して実行することをお勧めします。とりあえず RoMapという名称(仮称)を命名してみました。コンセプトとしては JSourceCodeWizardをベースになっていますが、ソースコードは ほぼスクラッチでの書き下ろしです。(精神は強く引き継いでいます)
ちょいで作成したにしては 良くできていると思います。ただし、動作確認は SQL Server 2000でしか行っていません。しかも試したのはソースコード自動生成の部分のみで、生成されたソースコードのテストは未実施です。また、とりあえず Oracleで動作するのかどうか非常に興味があります。
2004.01.23 追記 RDB指向なツールであれば、VIEWやテーブルJOIN付きSELECTはサポートした方が良いというご意見をWeb日記経由で頂きました。貴重なご意見、どうもありがとうございます。早速仕様案に追記させて頂きました。
技術的に JOIN付きRDBアクセスを実現しようとしたら、必ず設定ファイルが必要になってくるので、ちと敬遠していました。(外部キー設定をきっかけにJOINを憶測するという手もありますが、それもやっかいであると想像しています。)しかしなるほど 追加での設定ファイル指定による追加メソッド自動生成ができたら、やはりそれは便利というか現実に即していますよね。(初期の構想としてはテーブルJOINは 自動生成されたクラスを継承して実装することを想定していました。)この機能を取り込めたら RDB指向なツールになる事でしょう。
少し横道にそれて、いつまでRDB全盛時代が続くのか、という事ですが、まだ2-3年はRDB主流な時代は続くと私は想像しています。ま、これはあっとおどろくORDBやODBなどが登場したら一気に変わりうる点でもありますが…。
あと SqletというSeasarの一部を担うツールが 私のコンセプトに少し近い様子です。まだ その Sqletの仕様は見ていないのでどのように一致していてどのように不一致なのかは不明ですが…。
2005.02.14 追記 結局 blancoDBというR-Oマッピングによるアプローチに落ち着きました。
2005.03.17 後日談 この提案は結実し、最終的には blancoDbという R-Oマッピングツールの公開に至りました。
SRAの松田さんから 応援のメールを頂きました。大変心強いです。ありがとうございます。PostgreSQLで行き詰まった時は応援頼みます。
スガさんのツッコミ Subject: [igapyon:01258] 2004/01/21 日記 : ソースコード生成タイプの R-O マッピングスガです。ソースコード生成タイプのR-Oマッピング について、色々機能限定ですが、最近似たようなものを作成しました。お仕事ですのでそれを出すわけにはいきませんが (^^;
ソースコード生成にはテンプレートエンジン Velocity を使用 生成するソースコードのカスタマイズを楽にするため
生成後のクラスを利用する際には特にライブラリを必要としない 当然 JDBC ドライバは必要ですが :-)
1レコードに対応する ValueObject を生成
1テーブルに対応する DataAccessObject を生成 (VO を扱う)
機能は INSERT, UPDATE, DELETE と条件無し or 主キー条件 SELECT
ソースコードの生成の種は RDB スキーマを定義する XML ファイル RDB 設計そのままでは都合が悪い場合のワンクッション。クラス名、フィールド名を変えたい場合など。 例えば XMLHoge を XmlHoge とするか XMLHoge とするかはプロジェクトごとの命名規則によりますよね。
RDB スキーマを読み込み上記 XML ファイルを生成するツールも作成 Ant で連続実行させればスキーマからクラス生成にはなりますね。
RDB のデータ型と Java の型を関連付けする設定ファイルが必要 INTEGER を int にするとか VARCHAR を String にするとか。
単純なことしか出来ない代わりに RDBMS に依存しない (はず) RDBMS 特有の型を使う場合には非依存なんて無理ですけど。
JOIN に関しては、単純なものであれば View を作ることで対応する方が早いと思いますけど、如何でしょう。もちろん INSERT 等は出来ない場合が多いでしょうから SELECT 限定で。> 自動生成されたソースファイルは変更を加えない運用を想定する実際には生成したクラスをそのまま使うということはしていません。extends して利用、ではなく、そのまま変更してしまっています。単純利用であれば変更不要ですが、なかなかそういうことはありません。
ちょっと前の OO ML で、自動生成したソースコードはすぐにコンパイルした後削除してしまう、というアイデアが出ていました。これだと自動生成ソースコードの変更を行わない運用を徹底できますね。やるとしたら Ant 任せで良いでしょうし、ツール側で考慮する必要は無いでしょうけど。
--------------------------------------------------------------スガ
Subject: [igapyon:01260] 2004/01/21 日記 : ソースコード生成タイプのR-Oマッピングを開発しませんか?渡辺義則(Aさん)@好きになれないRDBです。私も自動生成ツールを持ってます。紹介しようと思ったのですが、ちょっと面白いアイディアが浮かんだのでそれを実装してから出します。(早ければ月曜夜)多分、腕に自信のある人は、みんな何らかのツールを持っているんじゃないでしょうか? ----渡辺義則
ここからいがぴょんいがぴょんです。(あんまりメールのお返事書けて無くてすみません)みなさま ツッコミ ありがとうございます。ツワモノな方々は 何かしら自動生成ツールを自作された経験があるものなのですね。勉強になります。(そういう私も O-Rマップで2作目ですから…)オシゴトなツールって 公開できないから その点 仕事時間外の趣味でオープンソースで作っちゃったものをオシゴトで利用、っていうのもアリかと思う今日この頃。渡辺さんの公開作品も楽しみにしています。あと、テーブルJOINについてですが、SQL文は手で書いてXMLに格納し、追加でパラメータと型のみを指定する。で何かしらデータが入っているとして、これをSELECTしてメタデータから型を取得して後ソースコードを自動生成を、と思っています。とにかくなるべく 特殊な設定は行わない、独自のものを極力導入しない、そしてターンキーに利用できる、というのを狙っています。スガさんのねらいと少し似ているのですが、昔作った O-Rマッピングツール(JSourceCodeWizard)に RDBスキーマから設定ファイルを自動生成するっていうツールをJSourceCodeWizardに機能追加してみたりしていました。いろいろJSourceCodeWizardでの反省を思い出しつつ、RoMapの実装方法を検討中…。
あおのさんのツッコミあおの です。> オシゴトなツールって 公開できないから その点 オープンソースで作っちゃったものをオシゴトで利用、っていうのもアリかと思う今日この頃。最近、特に思います。仕事で使いまわしができそうなパッケージ作ったけど、このお仕事終わったらお蔵入りになっちゃいそうなが、良くあります。開発者のエゴかもしれないですけど、いいものができたら、他の人に見せたいとう欲望が(笑)
渡辺義則さんのツッコミ渡辺義則(Aさん)です。私の自動生成ツールをUPしました。
最初は伊賀さんのみたいに、生成するプログラムはコードの中に埋め込んでいたのですが、テンプレートファイルから読み込むように変えました。ちょうどServletとJSPの関係みたいな感じです。それと、DBに依存する部分は別クラスにしました。対応しているのは、OracleとMySQLです。他のDBにも容易に対応できるはずです。まだ全然使い込んでないので、いくつか修正が必要かもしれません。よければ使ってみてください。 ----渡辺義則
ここからいがぴょんさっそくプロダクトがポストされて びっくりしました。渡辺さん、やりますなぁ。さっそくソースファイルを閲覧させて頂きます。
2004.01.26 Not Enough Resource 日記におけるツッコミ※要旨
O/RではなくてR-Oマッピングなところがミソ
必ず、より低レベルのAPIを使えるような手段を残しておく
組み合わせられるようにしておく
(Generation Gapパターンなどを使うなど)機能拡張にソースコードは変更しないのが理想
R-Oマッピングということなら、テーブル主導でオブジェクトが生成されるので、まず テーブルを列挙するトイプログラムが必要になります。とりあえずで書いたので SQL Server用の実装になっています。
/**
* テーブルの列挙および項目の列挙の挙動を確認するためのTOYプログラム
* Copyright (C) 2004 t.iga
*
* 余力があれば getCrossReferenceも検討したい。
*/
import java.sql.*;
public class EnumTableSample
{
public static final void main(String[] args) {
Connection conn=null;
try{
DriverManager.registerDriver(new com.microsoft.jdbc.sqlserver.SQLServerDriver());
conn=DriverManager.getConnection(
"jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=Northwind",
"sa", "");
DatabaseMetaData metadata = conn.getMetaData();
enumTables(metadata);
}catch(SQLException ex){
ex.printStackTrace();
}finally{
try{
conn.close();
}catch(SQLException ex){
ex.printStackTrace();
}
}
}
public static final void enumTables(DatabaseMetaData metadata) throws SQLException{
ResultSet resultSet = null;
try{
resultSet = metadata.getTables(null, null, null, null);
for(;resultSet.next(); ){
String tableTyle = resultSet.getString("TABLE_TYPE");
// タイプがTABLEであるもののみ扱う。
if(tableTyle.equals("TABLE")){
String tableCat = resultSet.getString("TABLE_CAT");
String tableSchem = resultSet.getString("TABLE_SCHEM");
String tableName = resultSet.getString("TABLE_NAME");
System.out.println(tableName
+ " (" + tableCat + "," + tableSchem + ")");
enumPrimaryKey(metadata, tableCat, tableSchem, tableName);
enumColumns(metadata, tableCat, tableSchem, tableName);
}
}
}finally{
if(resultSet!=null){
resultSet.close();
}
}
}
public static final void enumColumns(
DatabaseMetaData metadata, String tableCat, String tableSchem, String tableName)
throws SQLException {
ResultSet resultSetColumns = null;
try{
resultSetColumns = metadata.getColumns(tableCat, tableSchem, tableName, null);
for(;resultSetColumns.next();){
System.out.println(" [No."
+ resultSetColumns.getInt("ORDINAL_POSITION") + "] カラム名:"
+ resultSetColumns.getString("COLUMN_NAME"));
switch(resultSetColumns.getShort("DATA_TYPE")){
case java.sql.Types.CHAR:
System.out.println(" CHAR");
break;
case java.sql.Types.VARCHAR:
System.out.println(" VARCHAR");
break;
case java.sql.Types.LONGVARCHAR:
System.out.println(" LONGVARCHAR");
break;
case java.sql.Types.DATE:
System.out.println(" DATE");
break;
case java.sql.Types.TIMESTAMP:
System.out.println(" TIMESTAMP");
break;
case java.sql.Types.DECIMAL:
System.out.println(" DECIMAL");
break;
case java.sql.Types.INTEGER:
System.out.println(" INTEGER");
break;
case java.sql.Types.SMALLINT:
System.out.println(" SMALLINT");
break;
default:
System.out.println(
" サポート外のタイプ:"
+ resultSetColumns.getShort("DATA_TYPE"));
break;
}
/*
ARRAY 2003
public static final int BIGINT -5
public static final int BINARY -2
public static final int BIT -7
public static final int BLOB 2004
public static final int BOOLEAN 16
public static final int CLOB 2005
public static final int DATALINK 70
public static final int DISTINCT 2001
public static final int DOUBLE 8
public static final int FLOAT 6
public static final int JAVA_OBJECT 2000
public static final int LONGVARBINARY -4
public static final int NULL 0
public static final int NUMERIC 2
public static final int OTHER 1111
public static final int REAL 7
public static final int REF 2006
public static final int STRUCT 2002
public static final int TIME 92
public static final int TINYINT -6
public static final int VARBINARY -3
*/
System.out.println(" TYPE_NAME:"
+ resultSetColumns.getString("TYPE_NAME"));
System.out.println(" COLUMN_SIZE:"
+ resultSetColumns.getInt("COLUMN_SIZE"));
System.out.println(" DECIMAL_DIGITS:"
+ resultSetColumns.getInt("DECIMAL_DIGITS"));
System.out.println(" CHAR_OCTET_LENGTH:"
+ resultSetColumns.getInt("CHAR_OCTET_LENGTH"));
switch(resultSetColumns.getInt("NULLABLE")){
case DatabaseMetaData.columnNoNulls:
System.out.println(" 非NULL");
break;
case DatabaseMetaData.columnNullable:
System.out.println(" NULLを許可");
break;
case DatabaseMetaData.columnNullableUnknown :
System.out.println(" NULL不明");
break;
}
String strDefaultValue = resultSetColumns.getString("COLUMN_DEF");
if(strDefaultValue != null){
System.out.println(" デフォルト値:" + strDefaultValue);
}
}
}finally{
if(resultSetColumns!=null){
resultSetColumns.close();
}
}
}
public static final void enumPrimaryKey(
DatabaseMetaData metadata, String tableCat, String tableSchem, String tableName)
throws SQLException {
ResultSet resultSet = null;
try{
resultSet = metadata.getPrimaryKeys(tableCat, tableSchem, tableName);
System.out.println(" PrimaryKey");
for(;resultSet.next();){
System.out.println(" COLUMN_NAME:" + resultSet.getString("COLUMN_NAME"));
System.out.println(" KEY_SEQ:" + resultSet.getShort("KEY_SEQ"));
System.out.println(" PK_NAME:" + resultSet.getString("PK_NAME"));
}
}finally{
if(resultSet!=null){
resultSet.close();
}
}
}
}
関連する日記
関連するプロダクト
関連するキーワード
べったべたの初心者をJava技術者に育てるためには何が必要なのだろう?と思ったら、昔の日記を思い出しました。
さて、3年以上経過して、状況は変わったのかしらん。ゆっくり考えてみようと思います。