Daoフレームワーク DoMa

この前作るといったAnnotation Processing API を利用したフレームワークですが、DoMa(ドマ : Domain Oriented MApping Framework)という名前にしました。

とりあえずDBの簡単な更新処理ができるようになりました!

コードはここにあります。

簡単なデモはdoma-demoというプロジェクトになっています。

Eclpseの3.5 M7であればチェックアウトして簡単に動作を試せると思います。おそらく3.4の最初のバージョンだと動かないと思います。パラメータの名前がとれないんです。
もちろんjavacでも動きます(今のところビルドすると警告っぽいのがでるけど)。

ほかのIDEではわからないです。Annotation Processing APIは標準のAPIなので対応してくれていても不思議はないですがどうなんでしょうか。

作成するもの

最初の構想どおり、EntityもDaoもインタフェースで記述します。実装はAPTが作ってくれます。こんな感じで作ります。

Entity
@Entity
public interface Employee {

    @Id
    IntegerDomain id();

    StringDomain name();

    @Version
    IntegerDomain version();
}

テーブル名を指定するための@Tableやカラム名を指定するための@Columnも使えます。同じ名前にしているだけでJPAのものとはちがう独自アノテーションです。今のところ依存jarはなしです。

Dao
@Dao(config = DemoConfig.class)
public interface EmployeeDao {

    @Insert
    Integer insert(Employee e);

    @Update
    Integer update(Employee e);

    @Delete
    Integer delete(Employee e);
}

@Daoでは、設定が記述されたクラスを参照します。主にデータソースと関連付けるために使います。

簡単なものだけですが、例えばメソッドにアノテーションを指定しなかったり、パラメータを全く指定しなかったり、パラメータにエンティティ以外のものを指定したりするとエディタ上にエラーを表示するようにしています。
即座のバリデーションが、コード生成とならんでAPTの魅力ですね。

使用例

使用するコードではFactoryからEntityとDaoを取得して使います。今は、Entity(Dao)ごとに個別にFactoryを作っていますが、汎用的なものにまとめる予定です。

Client
public class Client {

    static DemoDataSource dataSource = new DemoDataSource();

    static EmployeeDao dao = EmployeeDaoFactory.newInstance();

    public static void main(String[] args) throws Exception {
        createTable();
        dumpEmployees();

        Employee emp1 = EmployeeFactory.newInstance();
        emp1.id().set(1);
        emp1.name().set("SMITH");

        Employee emp2 = EmployeeFactory.newInstance();
        emp2.id().set(2);
        emp2.name().set("ALLEN");

        dao.insert(emp1);
        dao.insert(emp2);
        dumpEmployees();

        emp1.name().set("hoge");

        dao.update(emp1);
        dao.delete(emp2);
        dumpEmployees();
    }

  private static void dumpEmployees() throws Exception {
        Connection connection = dataSource.getConnection();
        PreparedStatement preparedStatement = connection
                .prepareStatement("select id, name, version from employee order by id");
        ResultSet resultSet = preparedStatement.executeQuery();
        System.out.println("<dump all employees.>");
        while (resultSet.next()) {
            int id = resultSet.getInt(1);
            String name = resultSet.getString(2);
            int version = resultSet.getInt(3);
            System.out
                    .printf("id=%d, name=%s, version=%d\n", id, name, version);
        }
        System.out.println();
    }

    private static void createTable() throws Exception {
        Connection connection = dataSource.getConnection();
        PreparedStatement preparedStatement = connection
                .prepareStatement("create table employee (id integer primary key, name varchar(10), version integer)");
        preparedStatement.executeUpdate();
    }
}

DBはHSQLDB(Memory-Onlyモード)です。
処理の流れは、テーブルを作ってinsert、update、deleteをしています。
データを取得する方法がまだ実装できていないので、データの検索(データが変更されたかどうかのチェック)は直接JDBCで行っています。

実行結果



id=1, name=SMITH, version=1
id=2, name=ALLEN, version=1


id=1, name=hoge, version=2

insertやdeleteやupdateが行われていることがわかります。(まだ、SQLのログ機能もないので何やっているかわかりにくいですが)

雑感

EclipseのAPT(Annotation Processing API)はまだ若干バグっぽいところがありますね。Javacと挙動が違ったりします。今回困ったのは、javax.lang.model.util.Typesの isAssignableメソッドが正しい結果を返さないこと。なので、ある型のサブタイプだったらOKみたいなチェックが今のところできていません。あと、Factory Pathに指定したjarファイルの置き換え。どうも掴まれてしまうようで、置き換えるときはEclipse再起動しています。

これは、Eclipseでは問題ないのですが、javacでは自動生成したクラスへのimport文があると警告っぽいのが出ます。あれは何だろう。設定で何とかなるのか仕様なのかわかっていません。FQNだと大丈夫だったりするのでバグな気がします?

それにしても、Annotation Processing APIがムズイ。TypeMirrorとElementの間を行ったりきたりするんですが、簡単に相互変換ができないんですよね。すごく冗長なコードになっちゃう。


何の気兼ねもなくコードが書けて楽しいGWでした。もう明日1日か。