top / index / prev / next / target / source

2004-01-21 diary: ソースコード生成タイプのR-Oマッピングを開発しませんか?

いがぴょんの日記 日記形式でつづる いがぴょんコラム ウェブページです。

old-v2

ソースコード生成タイプのR-Oマッピングを開発しませんか?

いま世間的に望まれているのは RDBに近いレイヤーでのソースコード生成タイプのO-RマッピングならぬR-Oマッピングであるように思えます。

世のJavaな方々のWeb日記を見ていると、どうも みなさん納得のいくO-Rマッピングツールが無いようで

ここのところ 有識者の方々のWeb日記を見ていると、どうもみなさん 納得のいく O-Rマッピングツールを探して いろいろなオープンソースプロダクトを物色しまくられているようです。EJBは重すぎて、しかし世間の 『純粋な』O-Rマッピングの各種ツールはどうもなじめない。かといって RDBに近い種類 、分類するならば R-Oマッピングとでも表現できるようなツールはまだ現状世間的には一長一短である。、、、で 実は 私自身 私が完全にフィットして納得する O-Rマッピングツールが無くて 不満を感じている人の一人です。

O-RマッピングならぬR-Oマッピングツールをオープンソースで作成しようと思うのですが

  賛同される方はいらっしゃいますか?

納得行くプロダクトが既に存在はしていないのであれば、それじゃ新規に作ってしまえ、という乱暴な発想をしてみました。ええ、私は良くそういうことを思いつきます。特に オブジェクトとRDBとのマッピングには いつも気にしていて、そしてこだわりがある方なので、そこばかりは自作したって良いのではないかと考えたのです。(まえ一回自作した経験も持っていますし…)

ということで、賛同者の存在はさておき、下記のようなスペックのR/Oマッピングツールを構想中です。有識者のみなさんとりあえず興味を感じてもらえますか?

ポリシー

仕様案

  ↓
  指定されたテーブルに対するアクセサクラスのソースファイルが生成される。
  Tbl<テーブル名>.java
        用意するメソッドは下記の通り
  ↓
  特定のライブラリに依存せずに Java APIと共にコンパイルすることが可能であるようにする。

  ↓
  特定のライブラリに依存せずに 実行可能であるようにする。

実動としては こんなイメージ

う~ん。日本語と英語のマッピングに関する考慮は やはり必要だろうか… しかしいっそのこと、テーブルが日本語名だったらクラス名も日本語で良いかも…。

ここで大事なのは、おそらく 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マッピングツールの公開に至りました。

日記MLなどでの反応

SRAの松田さんから 応援のメールを頂きました。大変心強いです。ありがとうございます。PostgreSQLで行き詰まった時は応援頼みます。

スガさんのツッコミ Subject: [igapyon:01258] 2004/01/21 日記 : ソースコード生成タイプの R-O マッピングスガです。ソースコード生成タイプのR-Oマッピング について、色々機能限定ですが、最近似たようなものを作成しました。お仕事ですのでそれを出すわけにはいきませんが (^^;

JOIN に関しては、単純なものであれば View を作ることで対応する方が早いと思いますけど、如何でしょう。もちろん INSERT 等は出来ない場合が多いでしょうから SELECT 限定で。> 自動生成されたソースファイルは変更を加えない運用を想定する実際には生成したクラスをそのまま使うということはしていません。extends して利用、ではなく、そのまま変更してしまっています。単純利用であれば変更不要ですが、なかなかそういうことはありません。

ちょっと前の OO ML で、自動生成したソースコードはすぐにコンパイルした後削除してしまう、というアイデアが出ていました。これだと自動生成ソースコードの変更を行わない運用を徹底できますね。やるとしたら Ant 任せで良いでしょうし、ツール側で考慮する必要は無いでしょうけど。

参照:[oosquare-ml:04186]

この程度なら書いても大丈夫ですよね……?

別段珍しいアイデアというわけでもありませんし。

--------------------------------------------------------------スガ

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 日記におけるツッコミ※要旨

テーブルを列挙するためのトイプログラム

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初心者をエキスパートに成長させるには…

べったべたの初心者をJava技術者に育てるためには何が必要なのだろう?と思ったら、昔の日記を思い出しました。

さて、3年以上経過して、状況は変わったのかしらん。ゆっくり考えてみようと思います。

世間のニュースから () 2004


この日記について