Spring BootでMyBatis Generator のGradleプラグインを使用する

2020年6月17日

MyBatis Generator のGradleプラグインとは

MyBatis GeneratorをGradleのタスクとして実行するためのGradleプラグインです。 mybatis-generator-pluginこのプラグインを使用することでSpring BootプロジェクトのGradleから簡単にMyBatisジェネレータを実行できるようなります。

MyBatis Generatorプラグインの使用方法

本記事に掲載するサンプルの環境

以下のバージョンを使用しています。現在これらより新しいバージョンがリリースされているので適切なバージョンを使用して下さい。

  • Spring Boot 2.1.2 (Gradleプロジェクト)
  • Java11
  • PostgreSQL 11.6
  • MyBatis Generator Plugin 1.3.7
  • MyBatis-Generator-Core 1.3.7
  • MyBatis-Spring-Boot-Starter:2.1.1

build.gradleへ存関係を記述する

Spring Bootプロジェクトのbuild.gradleへMyBatis Generatorプラグインと、MyBatis Generator本体であるMyBatis Generator Coreへの依存関係を追加します。

また、Spring BootでMyBatisを使用するときにはMyBatis-Spring-Boot-Starterを使用すると設定が簡単になるのであわせてbuild.gradleへ追加します。

plugins {
    id 'org.springframework.boot' version '2.1.11.RELEASE'
    id 'io.spring.dependency-management' version '1.0.8.RELEASE'
    id 'java'

    // MyBatis Generatorプラグイン
    id "com.thinkimi.gradle.MybatisGenerator" version "2.1.2"

}

group = 'com.xxxxxx'
version = '0.0.1'
sourceCompatibility = '11'

mybatisGenerator {
    verbose = true
    // 設定ファイル
    configFile = "src/main/resources/generatorConfig.xml"

    dependencies {
        mybatisGenerator 'com.itfsw:mybatis-generator-plugin:1.3.7'
        mybatisGenerator 'org.mybatis.generator:mybatis-generator-core:1.3.7'
        mybatisGenerator 'org.postgresql:postgresql'
    }
}

configurations {
    ...
    mybatisGenerator
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'

    // MyBatisとPostgreSQL
    implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.1'
    implementation  'org.mybatis.generator:mybatis-generator-core:1.3.7'
    implementation group: 'org.postgresql', name: 'postgresql', version: '42.2.9'
    ...
}

この設定を追加することでGradleのタスクにmbGeneratorが追加されます。このタスクを実行することでDB上のスキーマからMyBatisのマッパー、Javaのエンティティクラスを生成することができます。

実際にmbGeneratorタスクを実行する前に設定ファイルの作成が必要です。上記のbuild.gradle中に記述されているsrc/main/resources/generatorConfig.xmlを作成します。

generatorConfig.xmlを作成する

generatorConfig.xmlは以下のようになります。このファイルには大まかに言うとMyBatis Generator自体の設定と接続先DB、対象テーブルを記述します。

MyBatisi Generatorによって生成されるマッパやエンティティクラスをカスタマイズするためのMyBatis Generatorのプラグインもあります。下の例ではhttps://github.com/itfsw/mybatis-generator-pluginのプラグインをいくつか使用しています。

注意点として、MyBatis GeneratorへのプラグインはjarとしてSprint Bootプロジェクトに組み込む必要があります。そのため、Mavenリポジトリに登録しない自作のプラグインを使用する場合、プラグインをGradleの子プロジェクトとして作成し、MyBatis Generator実行前にコンパイルしてjar形式する必要があります。

<?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="context1" >
        <!-- 楽観的ロックを行うためのプラグイン -->
        <plugin type="com.itfsw.mybatis.generator.plugins.OptimisticLockerPlugin">
            <property name="customizedNextVersion" value="false"/>
        </plugin>

        <!-- エンティティクラスでLombokを使用するためのプラグイン -->
        <plugin type="com.itfsw.mybatis.generator.plugins.LombokPlugin">
            <property name="@Data" value="true"/>
            <property name="@Builder" value="false"/>
            <property name="@AllArgsConstructor" value="true"/>
            <property name="@NoArgsConstructor" value="true"/>
            <property name="@Accessors(chain = true)" value="false"/>
            <property name="supportSuperBuilderForIdea" value="false"/>
        </plugin>

        <!-- マッパクラスのアノテーションを制御するためのプラグイン -->
        <plugin type="com.itfsw.mybatis.generator.plugins.MapperAnnotationPlugin">
            <property name="@Mapper" value="true"/>
            <property name="@Repository" value="true"/>
        </plugin>

        <!-- マッパxmlファイルを生成時に上書きするためのプラグイン -->
        <plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin"/>

     <!-- ページングを行うためのプラグイン -->
        <plugin type="org.mybatis.generator.plugins.RowBoundsPlugin"/>
        
        <!-- 生成される Java ファイルのコメントに日付を付与しない -->
        <commentGenerator>
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>

        <!-- 生成元となるDBへの接続情報 -->
        <jdbcConnection driverClass="org.postgresql.Driver" connectionURL="jdbc:postgresql://localhost:5432/testDB"
                        userId="yyyy" password="xxxx"/>

     <!-- MyBatis Generatorの設定 -->
        <javaTypeResolver>
            <property name="useJSR310Types" value="true"/>
        </javaTypeResolver>
        <javaModelGenerator targetPackage="com.example.demo" targetProject="src/main/java"/>
        <sqlMapGenerator targetPackage="com.example.demo" targetProject="src/main/resources"/>
        <javaClientGenerator targetPackage="com.example.demo" targetProject="src/main/java"
                             type="MIXEDMAPPER"/>
       
        <!-- 生成対象とするテーブルを記述する -->
            ...
            <table schema="public" tableName="product" >   
                <property name="versionColumn" value="version"/>
            </table>
       <table tableName="book">
            </table
            ...

</generatorConfiguration>

generatorConfig.xmlを作成したらmbGeneratorを実行しマッパ、エンティティクラスを生成することができます。

実行例

下記のDDLで作成したcategoryテーブルに対して上記のgeneratorConfig.xmlの設定でMyBatis Generatorを実行してみます。

CREATE TABLE category (
    id INTEGER,
    name TEXT NOT NULL,
    CONSTRAINT PRIMARY KEY(id) identity
);

MIXEDMAPPERを指定した場合生成されるファイルは以下の3つです。

  • Category.java (エンティティクラス)
  • CategoryMapper.java (マッパークラス)
  • CategoryExample.java (動的SQLのためのヘルパクラス)
  • CategoryMapper.xml (マッパーファイル)

Category.javaは以下のように生成されます。

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@NoArgsConstructor
@Data
@AllArgsConstructor
public class Category {
    private Integer id;

    private String name;

    private List<Book> books;
}

CategoryMapper.javaは以下のように生成されます。

import org.apache.ibatis.annotations.*;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Repository;

import java.util.List;

@Mapper
@Repository
public interface CategoryMapper {
    long countByExample(CategoryExample example);

    int deleteByExample(CategoryExample example);

    @Delete({
            "delete from category",
            "where id = #{id,jdbcType=INTEGER}"
    })
    int deleteByPrimaryKey(Integer id);

    @Insert({
            "insert into category (id, name)",
            "values (#{id,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR})"
    })
    int insert(Category record);

    int insertSelective(Category record);

    List<Category> selectByExampleWithRowbounds(CategoryExample example, RowBounds rowBounds);

    List<Category> selectByExample(CategoryExample example);

    @Select({
            "select",
            "id, name",
            "from category",
            "where id = #{id,jdbcType=INTEGER}"
    })
    @ResultMap("com.example.demo.CategoryMapper.BaseResultMap")
    Category selectByPrimaryKey(Integer id);

    int updateByExampleSelective(@Param("record") Category record, @Param("example") CategoryExample example);

    int updateByExample(@Param("record") Category record, @Param("example") CategoryExample example);

    int updateByPrimaryKeySelective(Category record);

    @Update({
            "update category",
            "set name = #{name,jdbcType=VARCHAR}",
            "where id = #{id,jdbcType=INTEGER}"
    })
    int updateByPrimaryKey(Category record);

}

CategoryMapperは以下のように生成されます。

package com.example.demo;

import java.util.ArrayList;
import java.util.List;

public class CategoryExample {
    protected String orderByClause;

    protected boolean distinct;

    protected List<Criteria> oredCriteria;

    public CategoryExample() {
        oredCriteria = new ArrayList<Criteria>();
    }

    public void setOrderByClause(String orderByClause) {
        this.orderByClause = orderByClause;
    }

    public String getOrderByClause() {
        return orderByClause;
    }

    public void setDistinct(boolean distinct) {
        this.distinct = distinct;
    }

    public boolean isDistinct() {
        return distinct;
    }

    public List<Criteria> getOredCriteria() {
        return oredCriteria;
    }

    public void or(Criteria criteria) {
        oredCriteria.add(criteria);
    }

    public Criteria or() {
        Criteria criteria = createCriteriaInternal();
        oredCriteria.add(criteria);
        return criteria;
    }

    public Criteria createCriteria() {
        Criteria criteria = createCriteriaInternal();
        if (oredCriteria.size() == 0) {
            oredCriteria.add(criteria);
        }
        return criteria;
    }

    protected Criteria createCriteriaInternal() {
        Criteria criteria = new Criteria();
        return criteria;
    }

    public void clear() {
        oredCriteria.clear();
        orderByClause = null;
        distinct = false;
    }

    protected abstract static class GeneratedCriteria {
        protected List<Criterion> criteria;

        protected GeneratedCriteria() {
            super();
            criteria = new ArrayList<Criterion>();
        }

        public boolean isValid() {
            return criteria.size() > 0;
        }

        public List<Criterion> getAllCriteria() {
            return criteria;
        }

        public List<Criterion> getCriteria() {
            return criteria;
        }

        protected void addCriterion(String condition) {
            if (condition == null) {
                throw new RuntimeException("Value for condition cannot be null");
            }
            criteria.add(new Criterion(condition));
        }

        protected void addCriterion(String condition, Object value, String property) {
            if (value == null) {
                throw new RuntimeException("Value for " + property + " cannot be null");
            }
            criteria.add(new Criterion(condition, value));
        }

        protected void addCriterion(String condition, Object value1, Object value2, String property) {
            if (value1 == null || value2 == null) {
                throw new RuntimeException("Between values for " + property + " cannot be null");
            }
            criteria.add(new Criterion(condition, value1, value2));
        }

        public Criteria andIdIsNull() {
            addCriterion("id is null");
            return (Criteria) this;
        }

        public Criteria andIdIsNotNull() {
            addCriterion("id is not null");
            return (Criteria) this;
        }

        public Criteria andIdEqualTo(Integer value) {
            addCriterion("id =", value, "id");
            return (Criteria) this;
        }

        public Criteria andIdNotEqualTo(Integer value) {
            addCriterion("id <>", value, "id");
            return (Criteria) this;
        }

        public Criteria andIdGreaterThan(Integer value) {
            addCriterion("id >", value, "id");
            return (Criteria) this;
        }

        public Criteria andIdGreaterThanOrEqualTo(Integer value) {
            addCriterion("id >=", value, "id");
            return (Criteria) this;
        }

        public Criteria andIdLessThan(Integer value) {
            addCriterion("id <", value, "id");
            return (Criteria) this;
        }

        public Criteria andIdLessThanOrEqualTo(Integer value) {
            addCriterion("id <=", value, "id");
            return (Criteria) this;
        }

        public Criteria andIdIn(List<Integer> values) {
            addCriterion("id in", values, "id");
            return (Criteria) this;
        }

        public Criteria andIdNotIn(List<Integer> values) {
            addCriterion("id not in", values, "id");
            return (Criteria) this;
        }

        public Criteria andIdBetween(Integer value1, Integer value2) {
            addCriterion("id between", value1, value2, "id");
            return (Criteria) this;
        }

        public Criteria andIdNotBetween(Integer value1, Integer value2) {
            addCriterion("id not between", value1, value2, "id");
            return (Criteria) this;
        }

        public Criteria andNameIsNull() {
            addCriterion("name is null");
            return (Criteria) this;
        }

        public Criteria andNameIsNotNull() {
            addCriterion("name is not null");
            return (Criteria) this;
        }

        public Criteria andNameEqualTo(String value) {
            addCriterion("name =", value, "name");
            return (Criteria) this;
        }

        public Criteria andNameNotEqualTo(String value) {
            addCriterion("name <>", value, "name");
            return (Criteria) this;
        }

        public Criteria andNameGreaterThan(String value) {
            addCriterion("name >", value, "name");
            return (Criteria) this;
        }

        public Criteria andNameGreaterThanOrEqualTo(String value) {
            addCriterion("name >=", value, "name");
            return (Criteria) this;
        }

        public Criteria andNameLessThan(String value) {
            addCriterion("name <", value, "name");
            return (Criteria) this;
        }

        public Criteria andNameLessThanOrEqualTo(String value) {
            addCriterion("name <=", value, "name");
            return (Criteria) this;
        }

        public Criteria andNameLike(String value) {
            addCriterion("name like", value, "name");
            return (Criteria) this;
        }

        public Criteria andNameNotLike(String value) {
            addCriterion("name not like", value, "name");
            return (Criteria) this;
        }

        public Criteria andNameIn(List<String> values) {
            addCriterion("name in", values, "name");
            return (Criteria) this;
        }

        public Criteria andNameNotIn(List<String> values) {
            addCriterion("name not in", values, "name");
            return (Criteria) this;
        }

        public Criteria andNameBetween(String value1, String value2) {
            addCriterion("name between", value1, value2, "name");
            return (Criteria) this;
        }

        public Criteria andNameNotBetween(String value1, String value2) {
            addCriterion("name not between", value1, value2, "name");
            return (Criteria) this;
        }
    }

    public static class Criteria extends GeneratedCriteria {

        protected Criteria() {
            super();
        }
    }

    public static class Criterion {
        private String condition;

        private Object value;

        private Object secondValue;

        private boolean noValue;

        private boolean singleValue;

        private boolean betweenValue;

        private boolean listValue;

        private String typeHandler;

        public String getCondition() {
            return condition;
        }

        public Object getValue() {
            return value;
        }

        public Object getSecondValue() {
            return secondValue;
        }

        public boolean isNoValue() {
            return noValue;
        }

        public boolean isSingleValue() {
            return singleValue;
        }

        public boolean isBetweenValue() {
            return betweenValue;
        }

        public boolean isListValue() {
            return listValue;
        }

        public String getTypeHandler() {
            return typeHandler;
        }

        protected Criterion(String condition) {
            super();
            this.condition = condition;
            this.typeHandler = null;
            this.noValue = true;
        }

        protected Criterion(String condition, Object value, String typeHandler) {
            super();
            this.condition = condition;
            this.value = value;
            this.typeHandler = typeHandler;
            if (value instanceof List<?>) {
                this.listValue = true;
            } else {
                this.singleValue = true;
            }
        }

        protected Criterion(String condition, Object value) {
            this(condition, value, null);
        }

        protected Criterion(String condition, Object value, Object secondValue, String typeHandler) {
            super();
            this.condition = condition;
            this.value = value;
            this.secondValue = secondValue;
            this.typeHandler = typeHandler;
            this.betweenValue = true;
        }

        protected Criterion(String condition, Object value, Object secondValue) {
            this(condition, value, secondValue, null);
        }
    }
}

CategoryMapper.xmlは以下のように生成されます。このファイルにはResultMapの定義などが記述されています。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.CategoryMapper">
  <resultMap id="BaseResultMap" type="com.example.demo.Category">
      <id column="id" jdbcType="INTEGER" property="id"/>
      <result column="name" jdbcType="VARCHAR" property="name"/>
  </resultMap>
  <sql id="Example_Where_Clause">
    <where>
      <foreach collection="oredCriteria" item="criteria" separator="or">
        <if test="criteria.valid">
          <trim prefix="(" prefixOverrides="and" suffix=")">
            <foreach collection="criteria.criteria" item="criterion">
              <choose>
                <when test="criterion.noValue">
                  and ${criterion.condition}
                </when>
                <when test="criterion.singleValue">
                  and ${criterion.condition} #{criterion.value}
                </when>
                <when test="criterion.betweenValue">
                  and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
                </when>
                <when test="criterion.listValue">
                  and ${criterion.condition}
                  <foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
                    #{listItem}
                  </foreach>
                </when>
              </choose>
            </foreach>
          </trim>
        </if>
      </foreach>
    </where>
  </sql>
  <sql id="Update_By_Example_Where_Clause">
    <where>
      <foreach collection="example.oredCriteria" item="criteria" separator="or">
        <if test="criteria.valid">
          <trim prefix="(" prefixOverrides="and" suffix=")">
            <foreach collection="criteria.criteria" item="criterion">
              <choose>
                <when test="criterion.noValue">
                  and ${criterion.condition}
                </when>
                <when test="criterion.singleValue">
                  and ${criterion.condition} #{criterion.value}
                </when>
                <when test="criterion.betweenValue">
                  and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
                </when>
                <when test="criterion.listValue">
                  and ${criterion.condition}
                  <foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
                    #{listItem}
                  </foreach>
                </when>
              </choose>
            </foreach>
          </trim>
        </if>
      </foreach>
    </where>
  </sql>
  <sql id="Base_Column_List">
    id, name
  </sql>
  <select id="selectByExample" parameterType="com.example.demo.CategoryExample" resultMap="BaseResultMap">
    select
    <if test="distinct">
      distinct
    </if>
      <include refid="Base_Column_List"/>
    from category
    <if test="_parameter != null">
        <include refid="Example_Where_Clause"/>
    </if>
    <if test="orderByClause != null">
      order by ${orderByClause}
    </if>
  </select>
  <delete id="deleteByExample" parameterType="com.example.demo.CategoryExample">
    delete from category
    <if test="_parameter != null">
        <include refid="Example_Where_Clause"/>
    </if>
  </delete>
  <insert id="insertSelective" parameterType="com.example.demo.Category">
    insert into category
    <trim prefix="(" suffix=")" suffixOverrides=",">
      <if test="id != null">
        id,
      </if>
      <if test="name != null">
        name,
      </if>
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides=",">
      <if test="id != null">
        #{id,jdbcType=INTEGER},
      </if>
      <if test="name != null">
        #{name,jdbcType=VARCHAR},
      </if>
    </trim>
  </insert>
  <select id="countByExample" parameterType="com.example.demo.CategoryExample" resultType="java.lang.Long">
    select count(*) from category
    <if test="_parameter != null">
        <include refid="Example_Where_Clause"/>
    </if>
  </select>
  <update id="updateByExampleSelective" parameterType="map">
    update category
    <set>
      <if test="record.id != null">
        id = #{record.id,jdbcType=INTEGER},
      </if>
      <if test="record.name != null">
        name = #{record.name,jdbcType=VARCHAR},
      </if>
    </set>
    <if test="_parameter != null">
        <include refid="Update_By_Example_Where_Clause"/>
    </if>
  </update>
  <update id="updateByExample" parameterType="map">
    update category
    set id = #{record.id,jdbcType=INTEGER},
      name = #{record.name,jdbcType=VARCHAR}
    <if test="_parameter != null">
        <include refid="Update_By_Example_Where_Clause"/>
    </if>
  </update>
  <update id="updateByPrimaryKeySelective" parameterType="com.example.demo.Category">
    update category
    <set>
      <if test="name != null">
        name = #{name,jdbcType=VARCHAR},
      </if>
    </set>
    where id = #{id,jdbcType=INTEGER}
  </update>
    <select id="selectByExampleWithRowbounds" parameterType="com.example.demo.CategoryExample"
            resultMap="BaseResultMap">
        select
        <if test="distinct">
            distinct
        </if>
        <include refid="Base_Column_List"/>
        from category
        <if test="_parameter != null">
            <include refid="Example_Where_Clause"/>
        </if>
        <if test="orderByClause != null">
            order by ${orderByClause}
        </if>
    </select>
  <select id="findAll" resultMap="BaseResultMap">
    SELECT
      <include refid="Base_Column_List"/>
      FROM category
  </select>
</mapper>