🦔

【MyBatis】spring bootでDB操作をできるだけ速く簡単に作成する【自動生成】

2022/04/13に公開

はじめに

spring boot + MyBatisDB操作をするまでを、できる限り速く、できるだけ簡単に、難しい設定もなく作りたかったので、いろいろ調べた結果を記事にしました。

  • 単純なselectinsertをするだけで、いろいろ手動で作りたくない。
  • カラム数が多いテーブルと、Modelクラスをマッピングするだけで大変。
  • この作業は、プログラムとしても単調でつまらなくて、虚無感が半端ない
  • 必要なファイルとかを自動生成するようなツールをExcelで作ってた現場あったけど、しばらくたって後任(俺)が新規テーブルを作るかーって時に動かなくなってた。投げ捨てたくなった。

ってな感じで、すごく面倒な作業をやめたかったので自動生成できるようにしたかった。

目標

  • 作成済みのテーブルからModelクラス、Mapperインターフェース、xmlファイルを自動生成。
  • DBの命名規則はsnake_case、Javaの命名規則はcamelCaseで作成されることを想定
  • 自動生成したファイルでのDB操作の動作確認
    ⇒ 自動生成分はselect , update , delete, insert の基本操作のみ想定
  • 手動で作成する分も自動生成したファイルを流用して、楽に作る

前提条件、実行環境とか

  • いろいろ設定とかあるが、できるだけシンプルでわかりやすい構成しているので、詳細はMyBatis Generator参照してください。
  • SQLはすべてXMLファイルで定義するように作ってます。
  • IDEは、STSを使ってます。
  • DB構築されていること前提にしています。今回の記事では、mySQLを利用しています。
    参考:Dockerでmysql構築
  • 下記のようなテーブルを対象に作成しています。
自動生成対象のテーブルとデータ作成
 CREATE TABLE tb_sample ( 
    id int NOT NULL AUTO_INCREMENT
    , product_name varchar (20) 
    , category_id varchar (8) 
    , PRIMARY KEY (id)
);

insert into tb_sample values(1, 'hogeA', 'A');
insert into tb_sample values(2,'hogeB', 'B');
insert into tb_sample values(3,'hogeC', 'C');
insert into tb_sample values(4,'fooA', 'A');
insert into tb_sample values(5,'fooB', 'B');
insert into tb_sample values(6,'sampleA', 'A');
insert into tb_sample values(7,'sampleB', 'C'); 

事前準備

MyBatis Generator インストール

[ヘルプ]⇒[eclipse マーケットプレイス]からMyBatis Generatorをインストール

spring boot プロジェクト作成

まだ、プロジェクトを用意していない人はここから作成
https://start.spring.io/

今回実施するのでとりあえず、必要最低限の↓のような感じで作成

依存関係はこんな感じ。

build.gradle(抜粋)
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.2'
	runtimeOnly 'mysql:mysql-connector-java'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

自動生成

Generatorファイル作成

任意の場所にmybatis-generator-config.xmlを作成します。
(公式サイトのExampleから自動設定xmlをコピーしていらないそうなところ削ってできるだけシンプルにしています)

ファイルの中身は↓のような感じです。

mybatis-generator-config.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="demoTables" targetRuntime="MyBatis3">
    <!-- DB接続設定 -->
    <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
      connectionURL="jdbc:mysql://localhost/hogedb"
      userId="hoge"
      password="passw0rd">
      <property name="nullCatalogMeansCurrent" value="true" />
    </jdbcConnection>
      
    <!-- Select結果がマッピングされる、Javaモデルの出力設定 -->
    <javaModelGenerator targetPackage="com.example.demo.db.model.generated" targetProject="demo/src/main/java">
      <property name="enableSubPackages" value="true" />
      <property name="trimStrings" value="true" />
    </javaModelGenerator>
      
    <!-- SQLを定義したXMLファイルの出力設定 -->
    <sqlMapGenerator targetPackage="com.example.demo.db.mapper.generated"  targetProject="demo/src/main/resources">
      <property name="enableSubPackages" value="true" />
    </sqlMapGenerator>
    
    <!-- Mapperインターフェースの出力設定 -->
    <javaClientGenerator type="XMLMAPPER" targetPackage="com.example.demo.db.mapper.generated"  targetProject="demo/src/main/java"> 
      <property name="enableSubPackages" value="true" />
    </javaClientGenerator>
    
    <!-- 自動生成するテーブルの設定 -->
    <table tableName="%"
      enableInsert="true"
      enableSelectByPrimaryKey="true"
      enableUpdateByPrimaryKey="true"
      enableDeleteByPrimaryKey="true"
      enableSelectByExample="false"
      enableUpdateByExample="false"
      enableDeleteByExample="false"
      enableCountByExample="false"
      selectByPrimaryKeyQueryId="false"
      selectByExampleQueryId="false"
      modelType="flat">
    </table>
  </context>
</generatorConfiguration>

DB接続設定

jdbcConnectionDB接続情報を設定します。

【MySQLのみ】
nullCatalogMeansCurrentのプロパティを設定しています。
ジェネレータが MySql 情報スキーマ(sys、information_schema、performance_schema など)のテーブルまで作ろうとしてしまうので、設定しときます。

詳細は:https://mybatis.org/generator/usage/mysql.html

モデル出力設定

javaModelGeneratorに、DBからselectした結果をマッピングするモデルクラスの出力設定をします。

SQL のXML定義ファイル出力設定

sqlMapGeneratorに、実行SQLが定義してある、xmlファイルの出力設定をします。

Mapperインターフェースの出力設定

javaClientGeneratorに、Mapperインターフェースの出力設定をします。
すべてのSQLxmlファイル経由で実行するように設定してます(type="XMLMAPPER" )

sqlMapGeneratortargetPackagejavaClientGeneratortargetPackageは、同じパッケージを合わせとくと、何も設定しないでも自動でxmlファイルを読み込んでくれます。
(合わせない場合は、mybatis.mapper-locationsapplication.propertiesで設定する)

自動生成対象のテーブル設定

tableに、出力対象のテーブル設定と、メソッドの種類とかを設定します。

  • tableName="%" ですべてのテーブルを対象にしています
    ⇒ すべてだと*(アスタリスク)かなと思って設定してたら少し嵌った…
  • modelType="flat"が一番シンプルに出力されます。(テーブルとモデルが1:1になる)
  • enable~, select~で自動生成するメソッドの種類を指定しています

他にも自動生成でやれそうだが、複雑になる場合は要件毎に対応すべきかなと思うので、このぐらいで十分だと思っている。

注意点

  • 実行環境がMySQLで確認しているが、少し特殊かもしれないです。
    というのも、mySQLはカタログとスキーマを適切にサポートしていないかららしい。なので、他の種類のDBで試してみるときは、以下のような点を確認してみるといいかも…?(mysql以外未確認)

    • tableタグにschema属性を指定してみる
    • enableSubPackagesプロパティの要/不要

実行

対象のプロジェクト右クリック⇒[実行構成]⇒[MyBatis Generator]の新規作成で、上で作ったxmlファイルを指定して実行する。

自動作成ファイル

下記のような感じでファイルが出力されました。

また、作成されたMapperインターフェースのメソッドは

  • deleteByPrimaryKey
  • insert
  • insertSelective
  • selectByPrimaryKey
  • updateByPrimaryKeySelective
  • updateByPrimaryKey

が作られてました。

Selectiveのありなしがあるメソッドは、モデルに設定された値がNULLの場合にNULLで更新するか、更新対象外のカラムにするかの違いです。

MySQLだとtext型などのカラムがあるテーブルは、~WithBLOBsというメソッドも作成される。ここら辺が、唐突に作られるとビビる。テーブルのカラム型のせいだってのを知っとくとビビらない。
個人的には、桁数はしっかりと決めて定義するほうなのでここら辺はあまり気にしない事にした。

動作確認

自動生成したファイルを、動かしてみる。

自動生成したMapperインターフェースの読み込み設定

@MapperScanアノテーションで自動生成したmapperインターフェースの出力先パッケージを設定する。

Mapperインターフェース自体に@Mapperアノテーションを付ける方法もあるが、これだと自動生成のたびに毎回やる必要があるのでNG

DemoApplication.java
package com.example.demo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.example.demo.db.mapper")
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

applicationでのDB設定

application.propertiesDBの接続設定など

pring.datasource.url=jdbc:mysql://localhost/hogedb
spring.datasource.username=hoge
spring.datasource.password=passw0rd
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
mybatis.configuration.map-underscore-to-camel-case=true

mybatis.configuration.map-underscore-to-camel-caseは、DB側の命名規則snake_caseとJava側の命名規則camelCaseとのマッピング用です。

画面表示で確認

動確用なので、Controllerで直接実行させちゃいます

SampleController
package com.example.demo.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;

import com.example.demo.db.mapper.generated.TbSampleMapper;
import com.example.demo.db.model.generated.TbSample;

@Controller
public class SampleController {

    @Autowired
    TbSampleMapper mapper;

    @GetMapping("/sample/select/{id}")
    @ResponseBody
    public String select(@PathVariable("id") Integer id) {
        TbSample sample = mapper.selectByPrimaryKey(id);

        return "product Name = " + sample.getProductName();
    }

    @GetMapping("/sample/update/{id}/{name}")
    @ResponseBody
    public String update(@PathVariable("id") Integer id, @PathVariable("name") String name) {
        TbSample sample = new TbSample();
        sample.setId(id);
        sample.setProductName(name);
        mapper.updateByPrimaryKeySelective(sample);
        return "Update!";
    }

    @GetMapping("/sample/insert/{name}/{category}")
    @ResponseBody
    public String insert(@PathVariable("name") String name, @PathVariable("category") String category) {

        TbSample sample = new TbSample();
        sample.setProductName(name);
        sample.setCategoryId(category);

        mapper.insert(sample);

        return "Insert!";
    }
}

ちゃんと実行できているの確認

手動作成分の追加

mapperファイル作成

com.example.demo.db.mapper.customのパッケージに手動作成分のファイルを作っていきます。
作成対象としては、category_idカラムの値でselectです。自動生成でも作れますが、とりあえず単純なのということで…

TbSampleCustomMapper.xml
<?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.db.mapper.custom.TbSampleCustomMapper">
  <select id="selectByCategory" parameterType="java.lang.String" resultMap="com.example.demo.db.mapper.generated.TbSampleMapper.BaseResultMap">
    select
     <include refid="com.example.demo.db.mapper.generated.TbSampleMapper.Base_Column_List" />
    from tb_sample
    where category_id = #{category,jdbcType=VARCHAR}
  </select>
</mapper>
TbSampleCustomMapper.java
package com.example.demo.db.mapper.custom;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;

import com.example.demo.db.model.generated.TbSample;

@Mapper
public interface TbSampleCustomMapper {
    public List<TbSample> selectByCategory(String category);
}

ポイントはxmlcom.example.demo.db.mapper.generated.TbSampleMapper.Base_Column_Listで、自動生成の方で定義したものを利用していること。
同じmodelクラスにマッピングしたくても、カラム数が多いテーブルだとそれだけで大変だし…これなら、テーブルにカラムが追加されても自動で反映してくれる。

実行確認

自動作成の実行確認の時のControllerに下記を追加

@Controller
public class SampleController {
    // 省略
    
    @Autowired
    TbSampleCustomMapper mapperCustom;
    
    // 省略

    @GetMapping("/sample/category/{category}")
    @ResponseBody
    public String selectCategory(@PathVariable("category") String category) {
        List<TbSample> samples = mapperCustom.selectByCategory(category);
        StringBuilder s = new StringBuilder();
        samples.forEach(sample -> {
            s.append("[ID=" + sample.getId() + ", PRODUCT_NAME=" + sample.getProductName() + "]<br>");
        });

        return s.toString();
    }
}


さいごに

自動生成したファイルを手で修正するのはNGってのは鉄則かなと思う。自動生成するたびに上書きされてしまうので。

出来る限り、自動で記述量を少なめにしてみたが…どうだったかな?
あまり本質と違う部分で虚無的プログラミングをしたくないので、まとめてみました。

Discussion