MyBatis Generatorを、Spring Bootアプリケーションと組み合わせて使うように構成してみる

# What's？

Spring Boot＋MyBatisな構成を始めてみようかなと思ったので、そのとっかかりとしてMyBatis Generatorを使うように構成してみます。

# お題

Spring BootアプリケーションにMyBatisを組み込むわけですが、以下の構成をお題とします。

- ModelおよびMapperはMyBatis Generatorを使って自動生成する
    - [MyBatis Generator](https://mybatis.org/generator/index.html)
- MyBatis Generatorは、Mavenプラグインとして組み込む
- 生成するMyBatisのMapperは、XML Mapperとする
- Mapperには`@Mapper`アノテーションを付与して、MyBatis Spring Boot Starterに自動検出させる
    - [MyBatis Spring Boot Starter / MyBatis Spring Boot AutoConfigure](http://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/index.html)
- 使用するデータベースはPostgreSQLとする

テーブルは、以下の定義とします。

```sql
create table pokemon (
  id serial,
  name text,
  primary key(id)
);

create table person (
  id integer,
  last_name text,
  first_name text,
  primary key(id)
);
```

# 環境

今回の環境は、こちら。

```shell
$ java --version
openjdk 11.0.14.1 2022-02-08
OpenJDK Runtime Environment (build 11.0.14.1+1-Ubuntu-0ubuntu1.20.04)
OpenJDK 64-Bit Server VM (build 11.0.14.1+1-Ubuntu-0ubuntu1.20.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.8.5 (3599d3414f046de2324203b78ddcf9b5e4388aa0)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 11.0.14.1, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-107-generic", arch: "amd64", family: "unix"
```

PostgreSQLは、14.2を使います。

```shell
$ docker container run \
  -it \
  --rm \
  --name postgres \
  -p 5432:5432 \
  -e POSTGRES_PASSWORD=password \
  postgres:14.2-bullseye
```

# Spring Bootプロジェクトを作成する

```shell
$ curl -s https://start.spring.io/starter.tgz \
  -d bootVersion=2.6.6 \
  -d javaVersion=11 \
  -d name=spring-boot-mybatis-generator-demo \
  -d groupId=com.example \
  -d artifactId=com.example \
  -d version=0.0.1-SNAPSHOT \
  -d packageName=com.example.spring.mybatis \
  -d dependencies=mybatis,postgresql \
  -d baseDir=spring-boot-mybatis-generator-demo | tar zxvf -
```

プロジェクト内に移動。

```shell
$ cd spring-boot-mybatis-generator-demo
```

自動生成された依存関係やプラグインの設定は、こちら。

```xml
	<properties>
		<java.version>11</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.2.2</version>
		</dependency>

		<dependency>
			<groupId>org.postgresql</groupId>
			<artifactId>postgresql</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
```

データベース接続設定のみ加えておきます。

```properties:src/main/resources/application.properties
spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=password
```

# プロジェクトにMyBatis Generatorを追加する

`pom.xml`に、MyBatis Generatorを組み込みます。

```xml
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
			<plugin>
				<groupId>org.mybatis.generator</groupId>
				<artifactId>mybatis-generator-maven-plugin</artifactId>
				<version>1.4.1</version>
				<configuration>
					<configurationFile>${project.basedir}/src/main/resources/generatorConfig.xml</configurationFile>
					<overwrite>true</overwrite>
					<includeAllDependencies>true</includeAllDependencies>
				</configuration>
			</plugin>
		</plugins>
	</build>
```

ドキュメントは、こちらですね。

https://mybatis.org/generator/running/runningWithMaven.html

`configurationFile`はデフォルト値と同じですが、MyBatis Generatorの設定ファイルを明示する意味で書いています。

`overwrite`を`true`として自動生成結果の上書きを可能にし、`includeAllDependencies`を`true`にすることで`runtime`スコープのPostgreSQL JDBCドライバーの依存関係を参照できるようにしています。

`includeAllDependencies`を使うのが嫌な場合は、プラグインの`dependency`にPostgreSQLのJDBCドライバーを追加すればよいでしょう。

```xml
			<plugin>
				<groupId>org.mybatis.generator</groupId>
				<artifactId>mybatis-generator-maven-plugin</artifactId>
				<version>1.4.1</version>
				<configuration>
					<configurationFile>${project.basedir}/src/main/resources/generatorConfig.xml </configurationFile>
					<overwrite>true</overwrite>
					<!-- <includeAllDependencies>true</includeAllDependencies> -->
				</configuration>
				<dependencies>
					<dependency>
						<groupId>org.postgresql</groupId>
						<artifactId>postgresql</artifactId>
						<version>${postgresql.version}</version>
					</dependency>
				</dependencies>
			</plugin>
```

MyBatis Generatorの設定は、こちら。

```xml:src/main/resources/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="tables" targetRuntime="MyBatis3">
        <plugin type="org.mybatis.generator.plugins.EqualsHashCodePlugin"/>
        <plugin type="org.mybatis.generator.plugins.MapperAnnotationPlugin"/>

        <jdbcConnection
                driverClass="org.postgresql.Driver"
                connectionURL="jdbc:postgresql://localhost:5432/postgres"
                userId="postgres"
                password="password"/>

        <javaModelGenerator
                targetPackage="com.example.spring.mybatis.model"
                targetProject="src/main/java"/>
        <sqlMapGenerator
                targetPackage="com.example.spring.mybatis.mapper"
                targetProject="src/main/resources"/>
        <javaClientGenerator
                type="XMLMAPPER"
                targetPackage="com.example.spring.mybatis.mapper"
                targetProject="src/main/java"/>

        <table tableName="pokemon">
            <generatedKey column="id" sqlStatement="JDBC"/>
        </table>
        <table tableName="person"/>
    </context>
</generatorConfiguration>
```

XML Mapperとするための前提として、`targetRuntime`を`MyBatis3`とします。

https://mybatis.org/generator/configreference/context.html

```xml
    <context id="tables" targetRuntime="MyBatis3">
```

生成されたMapperに`@Mapper`アノテーションを付与するため、`org.mybatis.generator.plugins.MapperAnnotationPlugin`プラグインを適用します。

https://mybatis.org/generator/reference/plugins.html

```xml
        <plugin type="org.mybatis.generator.plugins.EqualsHashCodePlugin"/>
        <plugin type="org.mybatis.generator.plugins.MapperAnnotationPlugin"/>
```

`org.mybatis.generator.plugins.EqualsHashCodePlugin`プラグインは、モデルの`equals`および`hashCode`を自動生成するようにオマケで追加しています。

Model、Mapper XMLファイル、Mapperの生成。

```xml
        <javaModelGenerator
                targetPackage="com.example.spring.mybatis.model"
                targetProject="src/main/java"/>
        <sqlMapGenerator
                targetPackage="com.example.spring.mybatis.mapper"
                targetProject="src/main/resources"/>
        <javaClientGenerator
                type="XMLMAPPER"
                targetPackage="com.example.spring.mybatis.mapper"
                targetProject="src/main/java"/>
```

`XMLMAPPER`を指定することで、Mapper XMLを使うようにMapperが生成されます。

https://mybatis.org/generator/configreference/javaClientGenerator.html

あとは、自動生成対象のテーブルを指定。

```xml
        <table tableName="pokemon">
            <generatedKey column="id" sqlStatement="JDBC"/>
        </table>
        <table tableName="person"/>
```

`generatedKey`は、主キーを自動採番するもの向けですね。

では、実行。

```shell
$ mvn mybatis-generator:generate
```

生成結果。

```shell
$ tree src/main/java/com/example/spring/mybatis/{mapper,model} src/main/resources/com/exampl
e/spring/mybatis/mapper
src/main/java/com/example/spring/mybatis/mapper
├── PersonMapper.java
└── PokemonMapper.java
src/main/java/com/example/spring/mybatis/model
├── Person.java
├── PersonExample.java
├── Pokemon.java
└── PokemonExample.java
src/main/resources/com/example/spring/mybatis/mapper
├── PersonMapper.xml
└── PokemonMapper.xml

0 directories, 8 files
```

Mapperインターフェースには、`@Mapper`アノテーションが付与されています。

```java
@Mapper
public interface PokemonMapper {
```

これで、MyBatis Spring Boot StarterのAutoConfigureでMapperを認識するようになります。

# 動かしてみる

MyBatis Generatorによる自動生成はできたので、軽く使って動作確認してみましょう。

こんなクラスを作成。

```java:src/main/java/com/example/spring/mybatis/DemoRunner.java
package com.example.spring.mybatis;

import com.example.spring.mybatis.mapper.PersonMapper;
import com.example.spring.mybatis.mapper.PokemonMapper;
import com.example.spring.mybatis.model.Person;
import com.example.spring.mybatis.model.Pokemon;
import com.example.spring.mybatis.model.PokemonExample;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
public class DemoRunner implements ApplicationRunner {
    Logger logger = LoggerFactory.getLogger(DemoRunner.class);

    PokemonMapper pokemonMapper;
    PersonMapper personMapper;

    public DemoRunner(PokemonMapper pokemonMapper, PersonMapper personMapper) {
        this.pokemonMapper = pokemonMapper;
        this.personMapper = personMapper;
    }

    @Transactional
    @Override
    public void run(ApplicationArguments args) throws Exception {
        Pokemon pikachu = new Pokemon();
        pikachu.setName("ピカチュウ");
        pokemonMapper.insert(pikachu);

        logger.info("pikachu id = {}", pikachu.getId());

        Pokemon fushigidane = new Pokemon();
        fushigidane.setName("フシギダネ");
        pokemonMapper.insert(fushigidane);

        pokemonMapper
                .selectByExample(new PokemonExample())
                .forEach(pokemon -> logger.info("pokemon[{}]: {}", pokemon.getId(), pokemon.getName()));

        Person tanjiro = new Person();
        tanjiro.setId(1);
        tanjiro.setLastName("竈門");
        tanjiro.setFirstName("炭治郎");

        personMapper.insert(tanjiro);

        Person result = personMapper.selectByPrimaryKey(1);
        logger.info("person[{}]: {} {}", result.getId(), result.getLastName(), result.getFirstName());
    }
}
```

動かしてみます。

```shell
$ mvn spring-boot:run
```

結果。

```shell
2022-04-08 20:56:32.355  INFO 141382 --- [           main] com.example.spring.mybatis.DemoRunner    : pikachu id = 1
2022-04-08 20:56:32.382  INFO 141382 --- [           main] com.example.spring.mybatis.DemoRunner    : pokemon[1]: ピカチュウ
2022-04-08 20:56:32.383  INFO 141382 --- [           main] com.example.spring.mybatis.DemoRunner    : pokemon[2]: フシギダネ
2022-04-08 20:56:32.388  INFO 141382 --- [           main] com.example.spring.mybatis.DemoRunner    : person[1]: 竈門 炭治郎
```

日時等を削除して、少し見やすく。

```shell
pokemon[1]: ピカチュウ
pokemon[2]: フシギダネ
person[1]: 竈門 炭治郎
```

OKですね。

簡単ですが、こんな感じで。