S2JDBC-Genでデータのバージョン管理をしないようにする

S2JDBC-Genは便利なのですが、バージョンごとにDDLとデータを追加していきます。変更を管理するのにはいいのでしょうが、どうもフォルダが増えていくのが気になります。そこでバージョン管理しないようにタスクを作ってみました。

SVNなどでDDLなどをバージョン管理されていることを前提として作っています。標準からはずれた使い方なので、本当にバージョン管理がいらないのかを考えてから利用しましょう!

標準の流れ

S2JDBC-Genは通常は以下の流れで作業を行います。

  1. gen-entity(データベースの構造からentityを作成する)
  2. entityの編集(シーケンスなどはここで付けたりします)
  3. gen-ddl(entityからDDLとデータを出力します)
  4. migrate(全てを削除して作成しなおしてデータ読み込み)

gen-entityは通常は1度のみ実行します。DBの構造を変更しても既存のファイルは上書きしません。ただしテーブルが追加された場合などの雛形は作成されますので、どんどんテーブルが増えていく場合には何度も呼ぶことになります。すでにentityを作成したテーブルの構造を変更した場合にはentityファイルを削除するか、entityファイルを編集します(編集が推奨)。
テーブルの追加も基本はEntityを自分で作成するのが推奨手順です。

entityの編集では、gen-entityでできた雛形などを編集します。oracleのシーケンスなどは自動的に作成されませんので、手で付け加える必要があります。この場合注意しないといけないのはすでにシーケンスがあったとしてもentityには反映されません。手で付け加えた場合にも昔のシーケンスなどがデータベースに残っていたりするので注意しましょう!

gen-ddlではdb/migrateフォルダにentityからDDLファイルとcsvでのデータを出力します。このコマンドを実行するたびにバージョン番号でmigrateフォルダ以下にフォルダが増えていきます。entityを編集したたびに実行する必要があります。まったく変更していない場合でもバージョンはあがっていきますので注意しましょう!

migrateでは、現在のバージョン番号のdropを実行してから、最新のバージョンのcreateを実行し、データも読み込みます。問題が発生する可能性としては、バージョン管理されていないデータベースに対して実行した場合です。その場合には0000のdropを削除して、データベースも手動ですべてのテーブルなどを削除します。

Antのコマンドを利用するのでjarを追加

(追記)
ant-1.7.0.jarをlibフォルダに入れて、.classpathにも追加しましょう!

改造の方向性

データベースがどんな状態からでもmaigrateを実行することにより、最新のデータベースになります。標準だとテストで自分のデータベースのみにあるテーブルなどは削除されずに残っていたりしますが、これをすべて削除してから最新の状態にします。

バージョンも利用せずに、すべて0001に保存していきます。これによりSVNでentityの差分などを追いやすくします。

バージョンフォルダの削除

これは初期S2JDBC-Gen導入テスト時にも役に立ちますが、antのdeleteタスクを利用してフォルダごと削除します。

  <target name="clean-migrate-dir">
    <delete includeEmptyDirs="true" >
      <fileset dir="db/migrate" />
      <fileset file="db/ddl-info.txt" />
    </delete>
  </target>

上記のようにant標準のタスクを利用します。ばっさり削除します。削除した状態でデータベースを操作すると状態を戻せなくなるので注意しましょう。基本的には各種操作前にデータベース全体をバックアップしてから作業を行いましょう!

データベースを削除するタスク作成

データベースのdropSQLベースからタスクベースに変更します。本当は各種データベースに対応する方がきれいですが、今回はOracleのみに対応したタスクになります。

S2JDBC-Genにコミットするのであれば全部対応しますが、プロジェクトの中で利用する場合にはべたべたにSQL書いてあった方が他の人が保守しやすいですからね。。。

src/main/java/org/seasar/extension/jdbc/gen/task/CleanDatabaseTask.java

package org.seasar.extension.jdbc.gen.task;

import org.seasar.extension.jdbc.gen.command.Command;
import org.seasar.extension.jdbc.gen.internal.command.CleanDatabaseCommand;

/**
 * データベースからテーブルなどを削除する{@link Task}です。
 * 
 * @author akira
 * @see CleanDatabaseCommand
 */
public class CleanDatabaseTask extends GenerateEntityTask {
    /** コマンド */
    protected CleanDatabaseCommand command = new CleanDatabaseCommand();

    @Override
    protected Command getCommand() {
        return command;
    }
}

上記がタスク部分になります。GenerateEntityTaskを継承していますので、必要な部分のみ記述します。getCommandは残しておかないとコマンドが実行されないようですので注意しましょう!

データベースを削除するコマンド作成

実際にはこの中で削除しています。

src/main/java/org/seasar/extension/jdbc/gen/task/CleanDatabaseTask.java

package org.seasar.extension.jdbc.gen.internal.command;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.seasar.extension.jdbc.util.ConnectionUtil;
import org.seasar.extension.jdbc.util.DataSourceUtil;
import org.seasar.framework.exception.SQLRuntimeException;
import org.seasar.framework.util.PreparedStatementUtil;
import org.seasar.framework.util.ResultSetUtil;
import org.seasar.framework.util.StatementUtil;

/**
 * データベースのテーブルなどをすべて削除するコマンドです。
 * 
 * @author akira
 */
public class CleanDatabaseCommand extends GenerateEntityCommand {
    @Override
    protected void doExecute() {
        dropTables();
        dropSequence();
    }

    private void dropTables() {
        String execSql = " select" +
                            "   tbl.TABLE_NAME NAME" +
                            " from" +
                            "   USER_TABLES tbl";
        Connection conn = DataSourceUtil.getConnection(jdbcManager
                .getDataSource());
        try {
            logger.debug(execSql);
            PreparedStatement ps = ConnectionUtil.prepareStatement(conn,
                    execSql);
            try {
                ResultSet rs = PreparedStatementUtil.executeQuery(ps);
                try {
                    while (rs.next()) {
                        String name = rs.getString("NAME");

                        String execSql2 = "DROP TABLE \"" + name + "\"";

                        try {
                            logger.debug(execSql2);
                            PreparedStatement ps2 = ConnectionUtil.prepareStatement(conn, execSql2);
                            try {
                                PreparedStatementUtil.executeQuery(ps2);
                            } finally {
                                StatementUtil.close(ps2);
                            }
                        } finally {
                        }
                    }
                } catch (SQLException e) {
                    throw new SQLRuntimeException(e);
                } finally {
                    ResultSetUtil.close(rs);
                }
            } finally {
                StatementUtil.close(ps);
            }
        } finally {
            ConnectionUtil.close(conn);
        }
    }

    private void dropSequence() {
        String execSql = " select" +
                            "   tbl.SEQUENCE_NAME NAME" +
                            " from" +
                            "   USER_SEQUENCES tbl";
        Connection conn = DataSourceUtil.getConnection(jdbcManager
                .getDataSource());
        try {
            logger.debug(execSql);
            PreparedStatement ps = ConnectionUtil.prepareStatement(conn,
                    execSql);
            try {
                ResultSet rs = PreparedStatementUtil.executeQuery(ps);
                try {
                    while (rs.next()) {
                        String name = rs.getString("NAME");

                        String execSql2 = "DROP SEQUENCE \"" + name + "\"";

                        try {
                            logger.debug(execSql2);
                            PreparedStatement ps2 = ConnectionUtil.prepareStatement(conn, execSql2);
                            try {
                                PreparedStatementUtil.executeQuery(ps2);
                            } finally {
                                StatementUtil.close(ps2);
                            }
                        } finally {
                        }
                    }
                } catch (SQLException e) {
                    throw new SQLRuntimeException(e);
                } finally {
                    ResultSetUtil.close(rs);
                }
            } finally {
                StatementUtil.close(ps);
            }
        } finally {
            ConnectionUtil.close(conn);
        }
    }
}

上記を見てもらえばわかりますが、べたべたのSQLを書いています。テーブルとシーケンスのみ削除していますんで、他に削除が必要なものがあれば追加しましょう。またトリガーはテーブルと一緒に消えたりするので登録していません。

タスクの登録

  <taskdef name="clean-database" classname="org.seasar.extension.jdbc.gen.task.CleanDatabaseTask" classpathref="classpath"/>

  (..中略..)

  <target name="clean-database">
    <clean-database
      rootpackagename="${rootpackagename}"
      entitypackagename="${entitypackagename}"
      javafiledestdir="${javafiledestdir}"
      javafileencoding="${javafileencoding}"
      env="${env}"
      jdbcmanagername="${jdbcmanagername}"
      classpathref="classpath"
    />
  </target>

上記のように上にクラスを登録いて、タスクを呼び出します。これでバージョンの削除とデータベースの削除ができるようになりました。

migradeタスクの変更

  <target name="migrate">
    <clean-database
      rootpackagename="${rootpackagename}"
      entitypackagename="${entitypackagename}"
      javafiledestdir="${javafiledestdir}"
      javafileencoding="${javafileencoding}"
      env="${env}"
      jdbcmanagername="${jdbcmanagername}"
      classpathref="classpath"
    />
    <migrate
      classpathdir="${classpathdir}"
      rootpackagename="${rootpackagename}"
      entitypackagename="${entitypackagename}"
      env="${env}"
      jdbcmanagername="${jdbcmanagername}"
      classpathref="classpath"
    />
  </target>

上記のようにmigradeする前にデータベースをすべて削除します。このままだとdropのタスクでエラーが出たりしますのでgen-ddlタスクも次で編集します。

gen-ddlタスクの変更

  <target name="gen-ddl">
    <delete includeEmptyDirs="true" >
      <fileset dir="db/migrate" />
      <fileset file="db/ddl-info.txt" />
    </delete>
    <gen-ddl
      (..中略..)
    />
    <gen-test
      (..中略..)
    />
    <gen-condition
      (..中略..)
    />
    <gen-names
      (..中略..)
    />
    <gen-service
      (..中略..)
    />
    <delete includeEmptyDirs="true" >
      <fileset dir="db/migrate/0000" />
    </delete>
  </target>

上記のように一番上と下にタスクを追加しています。一番上でバージョン関連のファイルをすべて削除しています。一番下では初期のdrop関係のファイルを削除しています。

これで以下のような手順になります。

gen-ddl
  1. バージョン関連のファイルを削除
  2. gen-ddlの処理(db/migrade/0000と0001を作成)
  3. 0000を削除
migrade時
  1. データベースの中身をすべて削除
  2. migradeの処理(0001のcreateの処理でテーブルなどとデータをロード)

ちなみに上記の状態だとデータベース上にバージョンを管理しているSCHEMA_INFOテーブルが残っていますが、これも邪魔だと思ったらmigradeの最後にexec-sqlDROP TABLE SCHEMA_INFOと記述したSQLを実行して消してあげてください。

感想

バージョン管理って便利なようで混乱しそうなんですよね。特にgen-ddlを複数の人がやる可能性がある場合って、gen-ddlを実行してからSVNで更新したら他の人がすでにgen-ddlを実行していて違うバージョンになっていた場合とかの衝突って回避するの難しいと思うんですよね。。。

あとoracleのバックアップファイルのインポート時にもすべて削除した状態から読み込ませたいので、clean-databaseタスクは作っておいたもいいのかもしれません。まあちょっと危ないですけれどね。。。

本当は違うことをやろうとしたけれど、気がついたらこんなものを作っていた、そんな土曜日の夜でした。。。

(追記)SVNで利用する場合には以下のエントリーも見てください

http://d.hatena.ne.jp/akiraneko/20081023/1224766423
S2JDBC-GENでバージョン管理をしない with SVNComments