2012-05-02

MyBatis Generator - 自動生成された要素を利用してカスタマイズする

MyBatis Generator とは、データベースのスキーマを元に MyBatis が使用する各種ファイルを自動生成するためのツールです。
MyBatis を使う場合は基本的に SQL を手書きする必要がありますが、大量の XML を手で書くのはミスの原因にもなりますし健康にも良くありません。また、スキーマ変更の多いプロジェクトでは更新の手間も馬鹿になりません。
それを解決するのが MyBatis Generator です。

MyBatis Generator は非常に柔軟で多機能なツールですが、この投稿ではマージ機能と生成されたファイルの拡張方法について説明します。
マージ機能とは、Generator がファイルを生成する際、既存のファイルを上書きするのではなく内容をマージしてくれる機能のことで、これのおかげでカスタマイズ内容を維持したまま繰り返し自動生成処理を実行することができるようになっています。



とりあえず Eclipse で MyBatis Generator を動かす説明をしていきます。
なぜ Eclipse かというと、バージョン 1.3.1 時点では Java ファイルのマージ機能を利用できるのは Eclipse プラグイン版の Generator のみだからです(※1)。

Eclipse プラグインのインストール

今回は Eclipse 3.7.2 + MyBatis Generator 1.3.1 という構成で説明します。まずは下記のアップデートサイトから MyBatis Generator の Eclipse プラグインをインストールします。
http://mybatis.googlecode.com/svn/sub-projects/generator/trunk/eclipse/UpdateSite/

テーブルの作成

説明のため、mbgtutorial というデータベースに personpet というテーブルを作成しました。ここでは MySQL を使っています。
CREATE TABLE person (
  id int,
  name varchar(32),
  gender varchar(8),
  PRIMARY KEY (id)
);

CREATE TABLE pet (
  pet_id int,
  owner_id int,
  pet_name varchar(32),
  PRIMARY KEY (pet_id)
);


プロジェクトの作成

mbg-tutorial という Maven プロジェクトを対象に説明します(m2e プラグインを使って作成しました)。作成直後は図のようなディレクトリ構成になっています。
pom.xml に mybatis の dependency を追加しておいてください。


MyBatis Generator の設定ファイルを用意する。

メニューから FileNewOther... を選び、Wizard 選択ダイアログで MyBatis Generator Configuration File を指定して次へ進みます。
ファイルの保存場所を指定して Finish ボタンを押します。ここでは Location/mbg-tutorialFile name はデフォルトの generatorConfig.xml のまま保存しました。
作成された設定ファイルを環境に合わせて編集します。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE generatorConfiguration 
  PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" 
  "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd" >
<generatorConfiguration>
  <classPathEntry location="/PATH_TO/mysql-connector-java-5.1.19.jar"/>
  <context id="context1">
    <commentGenerator>
      <property name="suppressDate" value="true" />
    </commentGenerator>
    <jdbcConnection driverClass="com.mysql.jdbc.Driver"
      connectionURL="jdbc:mysql://127.0.0.1/mbgtutorial" userId="root"
      password="" />
    <javaModelGenerator targetPackage="net.harawata.domain"
      targetProject="mbg-tutorial" />
    <sqlMapGenerator targetPackage="net.harawata.mapper"
      targetProject="mbg-tutorial/src/main/resources" />
    <javaClientGenerator targetPackage="net.harawata.mapper"
      targetProject="mbg-tutorial" type="XMLMAPPER" />
    <table tableName="person" alias="a_person"/>
    <table tableName="pet" />
  </context>
</generatorConfiguration>
編集が必要な要素について簡単に説明しておきます。
  • classPathEntry : JDBCドライバへのパスを入力します。
  • commentGenerator : タイムスタンプを含まないコメントを生成する設定です。
  • jdbcConnection : JDBC接続に関する設定です。
  • javaModelGenerator : Java Model クラスの生成先に関する設定です。
    • targetPackage : 生成先のパッケージを指定します。
    • targetProject : 対象となる Eclipse プロジェクト名を指定します。
  • sqlMapGenerator : XML Mapper ファイルの生成先に関する設定です。属性は javaModelGenerator と同じですが、Maven の規約に従って XML ファイルを src/main/resources 以下に生成するため、targetProject の指定を調整しています。
  • javaClientGenerator : Mapper クラスの生成先に関する設定です。属性に関する説明は javaModelGenerator と同じになるので省略します。
  • table : 生成対象となる各テーブルについての設定です。基本的に生成対象のテーブルごとに定義します。
    • schema : Schema の指定が必要な場合はここで指定します。
    • tableName : テーブル名です。SQL のワイルドカードを使用することもできます。


とりあえず生成してみる

保存した generatorConfig.xml を右クリックして、メニューから Generate MyBatis/iBATIS Artifacts を選択します。
正しくファイルが生成されれば、左図のようになるはずです。

Generator 固有のファイルは Example クラスのみですが、一応一通り説明しておきます。
Person.java, Pet.java はドメインオブジェクトです。
PersonExample.java, PetExample.java は生成された SQL を呼び出す際、条件を指定するために利用するクラスです。
PersonMapper.java, PetMapper.java はあなたのコードからクエリを実行するための Java インターフェイスです。 インターフェイスのみで実装クラスはありません(不要です)。
PersonMapper.xml, PetMapper は Mapper XML ファイルで、ほとんどの SQL はこのファイルに記述されています。

ファイルを開いてみると分かりますが、自動生成された Java, XML ファイルの各要素のコメントには @mbggenerated というアノテーションが含まれています。MyBatis Generator がマージ処理を行う際は、このアノテーションが付加された要素のみを一旦削除してから再度新しい要素を生成する、という処理が行われます。つまり、このアノテーションが付加されたメソッドやステートメントを直接編集してはいけないということです。

生成された Mapper を使うと、例えば主キーを指定して person テーブルの行を検索するコードは下記のように書くことができます。
Person person = personMapper.selectByPrimaryKey(123);
もう一つ、名前に 'tarou' を含む男性を検索して 'id' 順にソートして取得するコードは、Example クラスを使って下記のように書くことができます。
PersonExample example = new PersonExample();
example.createCriteria()
  .andNameLike("%tarou%")
  .andGenderEqualTo("male");
example.setOrderByClause("id");
List<Person> persons = personMapper.selectByExample(example);
このように自動生成されたファイルだけである程度のことはできるのですが、実際のソリューションでは色々と面倒な要件が出てくるのが当たり前です。ここからは、自動生成されたファイルをカスタマイズして行きます。

生成されたファイルの拡張

せっかく pet テーブルを作ったので、Person クラスに Pet のリストを追加して、Person を取得する際、飼っている Pet のリストも同時に取得できるようにします。

Person クラスにフィールドとアクセッサーメソッドを追加します。
private List<Pet> pets;

public List<Pet> getPets() {
 return pets;
}

public void setPets(List<Pet> pets) {
 this.pets = pets;
}
次に、PersonMapper.xml に、新しい resultMap を追加します。

<resultMap id="personWithPetsMap"
    type="net.harawata.domain.Person"
    extends="BaseResultMap">
  <collection property="pets" 
    resultMap="net.harawata.mapper.PetMapper.BaseResultMap"/>
</resultMap>
Generator が生成した BaseResultMap という resultMap を継承して、pets フィールドに対する <collection> 要素を追加しています。Pet 用の resultMap は別ファイルで定義されているので、namespace を含む完全修飾名で指定しています。

続けて、personpet を結合して取得する select 文を追加します。
<select id="selectPersonAndPetsByExample"
    parameterType="net.harawata.domain.PersonExample"
    resultMap="personWithPetsMap">
  select
  <include refid="Base_Column_List" />
  ,
  <include
    refid="net.harawata.mapper.PetMapper.Base_Column_List" />
  from person
  left join pet on pet.owner_id = id
  <if test="_parameter != null" >
    <include refid="Example_Where_Clause" />
  </if>
  <if test="orderByClause != null" >
    order by ${orderByClause}
  </if>
</select>
ここでも Generator が生成したカラムのリスト Base_Column_List を参照という形で再利用しています。

最後に、この select 文に対応するメソッドを PersonMapper インターフェイスに追加します。
List<Person> selectPersonAndPetsByExample(PersonExample example);
先ほど挙げた、名前に 'tarou' を含む男性を検索して 'id' 順にソートして取得するサンプルコードで、呼び出す Mapper メソッドを変更するだけで飼っている Pet のリストを含む Person のリストを取得できるようになります。
PersonExample example = new PersonExample();
example.createCriteria()
  .andNameLike("%tarou%")
  .andGenderEqualTo("male");
example.setOrderByClause("id");
List<Person> persons = 
  personMapper.selectPersonAndPetsByExample(example);


データベースの変更

ここでクライアントから要望があり、person テーブルに email カラムを追加しました。
ALTER TABLE person ADD COLUMN email varchar(255);
MyBatis Generator のマージ機能を試す良い機会です。再度、generatorConfig.xml ファイルを右クリックして Generate MyBatis/iBATIS Artifacts を実行してみましょう。
データベースに加えた変更が反映され、なおかつ先ほど手動で追加した変更が維持されているはずです。

また、今回は自動生成された要素を再利用していたので、データベース側の変更は手動で追加したコードにも反映されています。
person テーブルに追加した email カラムにデータを入力してから先ほどのサンプルコードを実行してみると、取得した Person オブジェクトの email フィールドに値が設定されていることが分かると思います。

Example クラスの拡張

最後に Example クラスを拡張する例として、先ほどの Person のリストを取得するサンプルで、飼っているペットの名前を条件として指定できるようにしてみます。
PersonExample.java で定義されている Criteria というインナークラスに、下記のメソッドを追加します。
public Criteria andPetNameEqualTo(String value) {
    addCriterion("pet_name =", value, "pet_name");
    return (Criteria) this;
}
この Criteria クラスのコメントには上で説明した @mbggenerated アノテーションが含まれています。ということは、自動生成を実行するたびに削除されてしまうのでしょうか。
もちろん違います。ここでは、@mbggenerated の後に do_not_delete_during_merge という文字列が続いていて、これによって Criteria クラスがマージの際に削除されないようになっているのです。

'mike' という名前のペットを飼っている person を取得するコードは下記のようになります。
PersonExample example = new PersonExample();
example.createCriteria()
  .andPetNameEqualTo("mike");
List<Person> persons = 
  personMapper.selectPersonAndPetsByExample(example);


ソースコード

ここで作成したプロジェクトを GitHub で公開しています。
https://github.com/harawata/mbg-tutorial
過程を追うことができるようにステップごとにコミットしています。
また、実際の動作を確認できるように HSQLDB のインメモリデータベースに対してクエリを実行するテストケースも入れてあります。

まとめ

要点をまとめておきます。
  • MyBatis Generator を使うと、データベースのテーブル定義から MyBatis の実行に必要なファイルを自動生成することができる。
  • MyBatis Generator Eclipse プラグインのマージ機能によって、カスタマイズ内容を維持したまま繰り返し自動生成処理を実行することができる(※1)。
  • MyBatis Generator が生成した要素を利用してカスタマイズすると、DB変更に伴うメンテナンスの手間を軽減することができる。

補足

ここで挙げたサンプルは説明を簡単にするため細かい条件を都合良く整えてあります。
MyBatis Generator は非常に柔軟にカスタマイズできるようになっていますので、もし実際のプロジェクトに適用して問題にぶつかったら、MyBatis Generator のリファレンスを参照してみてください。
尚、Eclipse プラグインのヘルプには、オンラインのリファレンスに記載されていない Eclipse プラグイン固有の内容も含まれていますので、Eclipse プラグインを使っている場合はそちらを参照することをおすすめします。


※1:XML ファイルについては、Eclipse プラグインを使っていない場合でもマージされます。また、Eclipse プラグイン版以外で Java ファイルのマージ機能を使う方法も一応あります。

0 件のコメント:

コメントを投稿