MyBatis GeneratorでBase_Column_Listのカラムにテーブル名修飾する方法

# はじめに
MyBatis Generatorでソース生成を行うと、SELECT句で使用するための``<sql>``タグの``Base_Column_List``に、テーブルの全カラムの情報が出力され、様々なselect句で使用することが出来ます。
これにより、``Base_Column_List``を利用してSELECT文を作成すれば、仕様変更でカラムの変更があっても、再度MyBatis Generatorを再実行することにより、SQL文の手作業での修正やエンティティの修正は不要となります。
しかし、この``Base_Column_List``、テーブル名修飾がされていないため、単一テーブルに対するSELECT文には有用ですが、テーブル結合を用いた場合のSELECT句には利用できないという欠点があります。

その問題点についての解決策を提示したいと思います。

# 解決策１
解決策の一つ目は、generatorConfig.xmlの``<table>``タグで``alias``指定をする方法です。

```xml:generatorConfig.xml
...
    
    <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ファイルは以下のように出力されます。

```xml:SQL定義用ファイルファイル
...
  <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)を継承して以下のように作成するかと思います。
```java
public class UserWithDepartment extends UserMaster {

	public String departmentName;

    // getter,setterは省略

}
```

そして、SQL定義用XMLファイルは以下のような記述になります。
```xml: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で格納先のエンティティクラスを指定するだけで済むようにしたいものです。

# 解決策２
上記の、generatorConfig.xmlファイルにすべてのテーブルについて定義しなくてはならない問題と、SQL定義ファイルに都度ResultMapを定義しなくてはならない問題の解決をするための方法として、MyBatis Generator用プラグインを別途作成して解決します。

別途プラグインを作成しなくてはならない、というデメリットは発生しますが、一度作成してしまえばそれ以降は使いまわしができるので、後々便利かとは思います。

## やり方
プラグインの作成方法は以下で説明してるのでプラグイン作成のための準備事項やプラグインの実行方法はこちらを参照してください。

https://qiita.com/norippe3/items/ed597c4a9a112fe8c4bc

### PluginAdapterのサブクラスの実装
PluginAdapterのサブクラスを作成し、以下のように実装します。
```java: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に作成したプラグインを設定


```xml:generatorConfig.xml
<generatorConfiguration>
  <context id=...>
	<!-- Base_Column_Listタグのカラム名リストにテーブル名を付与 -->
	<plugin type="com.example.test.AddTablenameToBaseColumnListPlugin"/>
    ...
```
generatorConfig.xmlに上記で作成したクラスをpluginタグに設定します。

### 生成されたファイルのサンプル
上記プラグインを設定後にMyBatis Generatorを実行した場合、以下のような出力となります。

```xml:生成された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="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となります。

```xml:SQL定義用XML
...
  <!-- 部署名つきでユーザを取得する -->
  <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>
...
```
:::note info
ただし、mybatis-confic.xmlの "mapUnderscoreToCamelCase"が"true"に設定されていることが前提となります。
:::

上記のように、``resultType``に値を格納するエンティティクラスを指定するだけでよく、``ResultMap``を別途定義する必要はありません。

# 終わりに
MyBatis Generatorの利点は、DBの情報をもとに自動でエンティティを作成したり単純なSQL定義を作成してくれることによｒ、これらの単純だけど面倒な作業から解放されることです。
そして、テーブルが追加になったりカラムが変更されたり場合にも再実行すれば最新のテーブル定義に合わせて再生成することができるため、特にテーブルの仕様が頻繁な開発段階では大変便利です。
しかし、一方で、デフォルトの状態だと、結合したテーブルでは使いまわしが利きにくいという点が気になっていました。

ここで紹介した方法は、それぞれメリットデメリットがあるので、状況により使い分けるのがよいかと思います。