📖

Nullableなカラムに@Nullableアノテーションを付与するMyBatis Generatorプラグインを作成する

2023/02/17に公開

概要

本記事は、MyBatis Generatorを導入済みの方向けに、NOT NULL制約の付いていないカラムに対応する Entityクラスのフィールドに@Nullableアノテーションを付与する方法を紹介しています。

ついでにAUTO_INCREMENTが設定されていない && NOT NULL制約が設定されているカラムには@Nonnullアノテーションを付与できるようにしているので、ぜひ試してみてください。

プラグインを作成する

MyBatis Generatorプラグインが担う役割は下記の2点です。

  • NOT NULL制約が設定されていないカラムに@Nullableアノテーションを付与すること
  • NOT NULL制約が設定されているカラムに@Nonnullアノテーションを付与すること
    • ただし、AUTO_INCREMENTが設定されている場合は付与しない
package xxx.xxx;

import java.util.List;
import java.util.stream.IntStream;

import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.api.dom.java.TopLevelClass;

public class ResolveNullablePlugin extends PluginAdapter {

    @Override
    public boolean validate(final List<String> warnings) {
        return true;
    }

    @Override
    public boolean modelBaseRecordClassGenerated(final TopLevelClass topLevelClass, final IntrospectedTable introspectedTable) {
        this.resolveNullable(topLevelClass, introspectedTable);
        return true;
    }

    @Override
    public boolean modelPrimaryKeyClassGenerated(final TopLevelClass topLevelClass, final IntrospectedTable introspectedTable) {
        this.resolveNullable(topLevelClass, introspectedTable);
        return true;
    }

    @Override
    public boolean modelRecordWithBLOBsClassGenerated(final TopLevelClass topLevelClass, final IntrospectedTable introspectedTable) {
        this.resolveNullable(topLevelClass, introspectedTable);
        return true;
    }

    private void resolveNullable(final TopLevelClass topLevelClass, final IntrospectedTable introspectedTable) {
        if (introspectedTable.getAllColumns().stream().anyMatch(IntrospectedColumn::isNullable)) {
            topLevelClass.addImportedType(new FullyQualifiedJavaType("javax.annotation.Nullable"));
        }
        if (introspectedTable.getAllColumns().stream().anyMatch(introspectedColumn -> !introspectedColumn.isNullable())) {
            topLevelClass.addImportedType(new FullyQualifiedJavaType("javax.annotation.Nonnull"));
        }

        IntStream.range(0, topLevelClass.getFields().size()).forEach(i -> {
            if (introspectedTable.getAllColumns().get(i).isNullable()) {
                topLevelClass.getFields().get(i).addAnnotation("@Nullable");
            }
            if (!introspectedTable.getAllColumns().get(i).isAutoIncrement() && !introspectedTable.getAllColumns().get(i).isNullable()) {
                topLevelClass.getFields().get(i).addAnnotation("@Nonnull");
            }
        });
    }

}

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>
    <context id="...">
        ...
        <plugin type="xxx.xxx.ResolveNullablePlugin"/>
        ...
    </context>
 </generatorConfiguration>

補足

AUTO_INCREMENT以外に、NOT NULL制約が付いているがNullableにしたいカラムとしてcreated_atupdated_atが考えられますが、本記事で紹介したプラグインはこれらに@Nonnullを付与するよう動作します。

`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

これらはRDBMSにデフォルト値の挿入を任せたいので、@Nullableアノテーションを付与した上でinsertSelectiveから挿入するのが狙いだと思います。
ただし、筆者はcreated_atupdated_atをアプリケーションから参照するのは悪手であると考えています。
created_at と updated_at のあつかいではアプリケーションから参照しないほうが良い理由と回避策を下記のように説明しています。

created_atupdated_at は RDB が値を格納する RDB ドメインのカラムであり、あくまで「レコードが作成された日時」と「レコードが更新された日時」をあらわしている。 アプリケーションがそれ以上の意味をもたせるのはよろしくない。

よって、created_atupdated_at を参照すれば済む場合でも、別にカラムをつくったほうがいい。 たとえば、記事の公開日は published_at、編集日は edited_at など。 これには、カラム名をアプリケーションのドメインによせる意味合いもある。 なお、published_atedited_at はアプリケーションが明示的に値を指定する。

まとめると、下記のようにすることで目的を達成できるはずです。

  • MyBatis Generatorがcreated_atおよびupdated_atを生成しないようにする
  • タイムスタンプ情報が欲しい場合は、具体的な名前のカラムを作成する

MyBatis Generatorがcreated_atおよびupdated_atを生成しないよう設定するには、下記のようにgeneratorConfig.xmlを編集してください。

<table tableName="%" delimitIdentifiers="true" delimitAllColumns="true">
    <ignoreColumn column="created_at"/>
    <ignoreColumn column="updated_at"/>
</table>

Discussion