Nullableなカラムに@Nullableアノテーションを付与するMyBatis Generatorプラグインを作成する
概要
本記事は、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_at
とupdated_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_at
とupdated_at
をアプリケーションから参照するのは悪手であると考えています。
created_at と updated_at のあつかいではアプリケーションから参照しないほうが良い理由と回避策を下記のように説明しています。
created_at
とupdated_at
は RDB が値を格納する RDB ドメインのカラムであり、あくまで「レコードが作成された日時」と「レコードが更新された日時」をあらわしている。 アプリケーションがそれ以上の意味をもたせるのはよろしくない。よって、
created_at
やupdated_at
を参照すれば済む場合でも、別にカラムをつくったほうがいい。 たとえば、記事の公開日はpublished_at
、編集日はedited_at
など。 これには、カラム名をアプリケーションのドメインによせる意味合いもある。 なお、published_at
やedited_a
t はアプリケーションが明示的に値を指定する。
まとめると、下記のようにすることで目的を達成できるはずです。
- 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