はじめに
MyBatis Generatorでソース生成を行うと、SELECT句で使用するための<sql>
タグのBase_Column_List
に、テーブルの全カラムの情報が出力され、様々なselect句で使用することが出来ます。
これにより、Base_Column_List
を利用してSELECT文を作成すれば、仕様変更でカラムの変更があっても、再度MyBatis Generatorを再実行することにより、SQL文の手作業での修正やエンティティの修正は不要となります。
しかし、このBase_Column_List
、テーブル名修飾がされていないため、単一テーブルに対するSELECT文には有用ですが、テーブル結合を用いた場合のSELECT句には利用できないという欠点があります。
その問題点についての解決策を提示したいと思います。
解決策1
解決策の一つ目は、generatorConfig.xmlの<table>
タグでalias
指定をする方法です。
...
<table schema="public" tableName="user_master" alias="u"><!-- aliasを設定する -->
enableSelectByExample="false"
enableDeleteByExample="false"
enableUpdateByExample="false"
enableCountByExample="false">
<property name="useActualColumnNames" value="false" />
...
</table>
上記のようにaliasを行うことにより、SQL定義用のxmlファイルは以下のように出力されます。
...
<resultMap id="BaseResultMap" type="com.example.test.model.UserMaster">
<!--
WARNING - @mbg.generated
This element is automatically generated by MyBatis Generator, do not modify.
-->
<id column="u_user_id" jdbcType="VARCHAR" property="userId" />
<result column="u_user_name" jdbcType="VARCHAR" property="userName" />
<result column="u_delete_flg" jdbcType="CHAR" property="deleteFlg" />
<result column="u_version" jdbcType="INTEGER" property="version" />
<result column="u_update_date" jdbcType="TIMESTAMP" property="updateDate" />
<result column="u_regist_date" jdbcType="TIMESTAMP" property="registDate" />
<result column="u_department_id" jdbcType="BIGINT" property="departmentId" />
</resultMap>
<sql id="Base_Column_List">
<!--
WARNING - @mbg.generated
This element is automatically generated by MyBatis Generator, do not modify.
-->
u.user_id as u_user_id, u.user_name as u_user_name, u.delete_flg as u_delete_flg,
u.version as u_version, u.update_date as u_update_date, u.regist_date as u_regist_date,
u.department_id as u_department_id
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.String" resultMap="BaseResultMap">
<!--
WARNING - @mbg.generated
This element is automatically generated by MyBatis Generator, do not modify.
-->
select
<include refid="Base_Column_List" />
from user_master u
where user_id = #{userId,jdbcType=VARCHAR}
</select>
...
上記の通り、各カラムはaliasで指定した値で修飾されるので、テーブル結合を行った場合においてもBase_Collumn_List
を使いまわすことが出来ます。
この方法の問題点
しかし、この方法については、いくつかの問題点があります。
まず、generatorConfig.xmlにすべてのテーブルについて定義を書かなくてはならないので、テーブルが多いと大変&テーブルの増減によりその都度当該ファイルを修正する必要があります。
もう一つの問題は、例えば、ユーザテーブル(user_master)と部署テーブル(department)があったとして、ユーザ情報に部署名を付随して取得したい、というケースがあったとします。
その場合、エンティティはユーザマスタエンティティ(UserMaster)を継承して以下のように作成するかと思います。
public class UserWithDepartment extends UserMaster {
public String departmentName;
// getter,setterは省略
}
そして、SQL定義用XMLファイルは以下のような記述になります。
...
<!-- 部署名つきのResultMap -->
<resultMap extends="BaseResultMap" id="UserWithDepartment" type="com.example.test.UserWithDepartment">
<result column="department_name" jdbcType="VARCHAR" property="departmentName" />
</resultMap>
<!-- 部署名つきでユーザを取得する -->
<select id="selectUserWithDepartment" parameterType="java.lang.String" resultMap="UserWithDepartment">
select
<include refid="Base_Column_List" />
,d.department_name
from user_master u,department d
where
u.department_id = d.department_id
and u.user_id = #{userId,jdbcType=VARCHAR}
</select>
上記のように、<resultMap>
タグのextends="BaseResultMap"
を記述することで、不足している項目を別途追加で定義します。
そして、<select>
タグのresultMapで別途定義したResultMapを指定します。
しかし、この方法は、その都度ResultMapを定義する必要があり面倒です。
理想は、別途ResultMapを定義することなく、resultTypeで格納先のエンティティクラスを指定するだけで済むようにしたいものです。
解決策2
上記の、generatorConfig.xmlファイルにすべてのテーブルについて定義しなくてはならない問題と、SQL定義ファイルに都度ResultMapを定義しなくてはならない問題の解決をするための方法として、MyBatis Generator用プラグインを別途作成して解決します。
別途プラグインを作成しなくてはならない、というデメリットは発生しますが、一度作成してしまえばそれ以降は使いまわしができるので、後々便利かとは思います。
やり方
プラグインの作成方法は以下で説明してるのでプラグイン作成のための準備事項やプラグインの実行方法はこちらを参照してください。
PluginAdapterのサブクラスの実装
PluginAdapterのサブクラスを作成し、以下のように実装します。
package com.example.test;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.xml.TextElement;
import org.mybatis.generator.api.dom.xml.XmlElement;
import org.mybatis.generator.codegen.mybatis3.MyBatis3FormattingUtilities;
import org.mybatis.generator.internal.util.StringUtility;
/**
* Base_Column_Listタグのカラム名リストにテーブル名を付与する
*/
public class AddTablenameToBaseColumnListPlugin extends PluginAdapter {
@Override
public boolean validate(List<String> warnings) {
return true;
}
@Override
public boolean sqlMapBaseColumnListElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
createBaseColumnListElement(element, introspectedTable, introspectedTable.getNonBLOBColumns());
return true;
}
@Override
public boolean sqlMapBlobColumnListElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
createBaseColumnListElement(element, introspectedTable, introspectedTable.getBLOBColumns());
return true;
}
private void createBaseColumnListElement(XmlElement element, IntrospectedTable introspectedTable,
List<IntrospectedColumn> columns) {
// aliasが指定されている場合は何もしない
if (StringUtility.stringHasValue(introspectedTable.getFullyQualifiedTable().getAlias())) {
return;
}
List<TextElement> answer = new ArrayList<TextElement>();
StringBuilder sb = new StringBuilder();
Iterator<IntrospectedColumn> iter = columns.iterator();
while (iter.hasNext()) {
// カラムにテーブル名を修飾
sb.append(
introspectedTable.getFullyQualifiedTable().getIntrospectedTableName() + "."
+ MyBatis3FormattingUtilities.getEscapedColumnName(iter.next()));
if (iter.hasNext()) {
sb.append(", ");
}
if (sb.length() > 80) {
answer.add(new TextElement(sb.toString()));
sb.setLength(0);
}
}
if (!sb.isEmpty()) {
answer.add(new TextElement(sb.toString()));
}
// 現在の要素をすべて削除して新たな要素に入れ替える
element.getElements().clear();
context.getCommentGenerator().addComment(element);
element.getElements().addAll(answer);
}
}
sqlMapBaseColumnListElementGenerated()
メソッドとsqlMapBlobColumnListElementGenerated()
メソッドをオーバーライドして、それぞれのタグの中身をテーブル名修飾されたカラムに入れ替える処理をしています。
generatorConfig.xmlに作成したプラグインを設定
<generatorConfiguration>
<context id=...>
<!-- Base_Column_Listタグのカラム名リストにテーブル名を付与 -->
<plugin type="com.example.test.AddTablenameToBaseColumnListPlugin"/>
...
generatorConfig.xmlに上記で作成したクラスをpluginタグに設定します。
生成されたファイルのサンプル
上記プラグインを設定後にMyBatis Generatorを実行した場合、以下のような出力となります。
...
<resultMap id="BaseResultMap" type="com.example.test.model.UserMaster">
<!--
WARNING - @mbg.generated
This element is automatically generated by MyBatis Generator, do not modify.
-->
<id column="user_id" jdbcType="VARCHAR" property="userId" />
<result column="user_name" jdbcType="VARCHAR" property="userName" />
<result column="delete_flg" jdbcType="CHAR" property="deleteFlg" />
<result column="version" jdbcType="INTEGER" property="version" />
<result column="update_date" jdbcType="TIMESTAMP" property="updateDate" />
<result column="regist_date" jdbcType="TIMESTAMP" property="registDate" />
<result column="department_id" jdbcType="BIGINT" property="departmentId" />
</resultMap>
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.example.test.model.UserMaster">
<!--
WARNING - @mbg.generated
This element is automatically generated by MyBatis Generator, do not modify.
-->
<result column="photo" jdbcType="BINARY" property="photo" />
</resultMap>
<sql id="Base_Column_List">
<!--
WARNING - @mbg.generated
This element is automatically generated by MyBatis Generator, do not modify.
-->
user_master.user_id, user_master.user_name, user_master.delete_flg, user_master.version,
user_master.update_date, user_master.regist_date, user_master.department_id
</sql>
<sql id="Blob_Column_List">
<!--
WARNING - @mbg.generated
This element is automatically generated by MyBatis Generator, do not modify.
-->
user_master.photo
</sql>
...
上記のように、Base_Column_List
やBlob_Column_List
のカラム名にテーブル名が付与されて出力されます。
生成されたファイルの使用例
これにより、例えば、テーブル結合を行うSQLに対して、Base_Column_List
を使用する場合、以下のようなSQLとなります。
...
<!-- 部署名つきでユーザを取得する -->
<select id="selectUserWithDepartment" parameterType="java.lang.String"
resultType="com.example.test.UserWithDepartment"> <!-- resultTypeに作成したエンティティクラスを設定 -->
select
<include refid="Base_Column_List" />
,d.department_name
from user_master,department d
where
user_master.department_id = d.department_id
and user_id = #{userId,jdbcType=VARCHAR}
</select>
...
ただし、mybatis-confic.xmlの "mapUnderscoreToCamelCase"が"true"に設定されていることが前提となります。
上記のように、resultType
に値を格納するエンティティクラスを指定するだけでよく、ResultMap
を別途定義する必要はありません。
終わりに
MyBatis Generatorの利点は、DBの情報をもとに自動でエンティティを作成したり単純なSQL定義を作成してくれることによr、これらの単純だけど面倒な作業から解放されることです。
そして、テーブルが追加になったりカラムが変更されたり場合にも再実行すれば最新のテーブル定義に合わせて再生成することができるため、特にテーブルの仕様が頻繁な開発段階では大変便利です。
しかし、一方で、デフォルトの状態だと、結合したテーブルでは使いまわしが利きにくいという点が気になっていました。
ここで紹介した方法は、それぞれメリットデメリットがあるので、状況により使い分けるのがよいかと思います。