LoginSignup
1
1

More than 1 year has passed since last update.

MyBatis GeneratorをKotlin + SpringBoot + MySQLで使う方法

Last updated at Posted at 2022-08-07

目的

Springフレームワークで人気のO/R Mapperの「Mybatis」
Mybatisを利用する際はSQL操作を記述するMapperクラスやデータを格納するModelクラスを定義する必要がありますがそれらのクラスをテーブル毎に自動で生成してくれるツールが「MyBatis Generator」です
今回はそのツールを利用してKotlinフォーマットで自動生成する方法を書きたいと思います

メリット

  • 単一テーブルに対するCRUD操作を簡潔に行うことができる
  • DDL変更に伴うコード修正が簡単

デメリット

  • 複数テーブルに対するRead操作の場合は個別でCustomMapperを作る必要がある
  • Kotlinの利点であるNull safetyが利用しづらい

環境

  • MacBookPro M1(zsh)
  • IntelliJ IDEA Community版
  • Docker Desktop(ローカルにMySQL8.0を立てるため)

成果物
GitHub: https://github.com/seki-shinnosuke/kotlin-mybatis-generator-sample

内容

やることは大きく3つです

  1. ローカルに自動生成したいDBを立てる
  2. Gradle(ビルドツール)に依存関係と自動生成タスクを記載
  3. Configファイル(XML)を記載

1. ローカルに自動生成したいDBを立てる

DDLは同じプロジェクト内で管理することをお勧めします
(今回の場合はddlフォルダに.sqlファイルを置きDocker初回実行と同時にDBを作ってくれるようにしています)

docker-compose.yml
version: '3'
services:
  db:
    image: mysql:8.0.30-oracle
    container_name: db
    networks:
      - sample-network
    environment:
      MYSQL_ROOT_PASSWORD: root
      TZ: 'Asia/Tokyo'
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    ports:
      - "33306:3306"
    volumes:
      - ./db/ddl:/docker-entrypoint-initdb.d
      - ./db/mysql_data:/var/lib/mysql
networks:
  sample-network:
    driver: bridge
% docker-compose up -d

2. Gradle(ビルドツール)に依存関係と自動生成タスクを記載

記載する依存関係とタスクは以下のような感じです

val mybatisGenerator: Configuration by configurations.creating
dependencies {
    mybatisGenerator("org.mybatis.generator:mybatis-generator-core:1.4.1")
    mybatisGenerator("mysql:mysql-connector-java:8.0.30")
}
task("mybatisGenerator") {
    doLast {
        ant.withGroovyBuilder {
            "taskdef"("name" to "mbgenerator", "classname" to "org.mybatis.generator.ant.GeneratorAntTask", "classpath" to mybatisGenerator.asPath)
        }
        ant.withGroovyBuilder {
            "mbgenerator"("overwrite" to true, "configfile" to "${rootDir}/db/mybatis/generatorConfig.xml", "verbose" to true)
        }
    }
}

SpringBootと掛け合わせて利用したい場合はアプリケーション側の依存関係に以下を追加する必要があります

implementation("org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.2")
implementation("org.mybatis.dynamic-sql:mybatis-dynamic-sql:1.4.0")

最終的なbuild.gradle.ktsは以下のようになります

build.gradle.kts
import io.spring.gradle.dependencymanagement.dsl.DependencyManagementExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

object Versions {
    const val jdk = "17"
}

plugins {
    application
    kotlin("jvm") version "1.6.10"
    kotlin("plugin.spring") version "1.6.10"
    id("org.springframework.boot") version "2.6.6"
    id("io.spring.dependency-management") version "1.0.11.RELEASE"
}

allprojects {
    group = "com.example"

    repositories {
        mavenCentral()
        maven("https://plugins.gradle.org/m2/")
    }

    tasks {
        // JSR 305チェックを明示的に有効にする
        withType<KotlinCompile>().configureEach {
            kotlinOptions.freeCompilerArgs = listOf("-Xjsr305=strict", "-java-parameters")
            kotlinOptions.jvmTarget = Versions.jdk
        }
    }
}

apply {
    plugin("kotlin")
    plugin("kotlin-spring")
    plugin("org.springframework.boot")
    plugin("io.spring.dependency-management")
}

dependencies {
    implementation("org.jetbrains.kotlin:kotlin-stdlib")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-gradle-plugin")
    implementation("org.springframework:spring-web")
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    // アプリ側でMybatisを利用するためのLibrary
    implementation("org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.2")
    implementation("org.mybatis.dynamic-sql:mybatis-dynamic-sql:1.4.0")

    runtimeOnly("mysql:mysql-connector-java")

    testImplementation("org.springframework.boot:spring-boot-starter-test") {
        exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
    }
}
// SpringBootBOMを使用して依存関係のバージョン管理
configure<DependencyManagementExtension> {
    imports {
        mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)
    }
}

/**
 * MyBatisGenerator設定
 */
val mybatisGenerator: Configuration by configurations.creating
dependencies {
    // mybatis-generator-coreはver1.4.0->1.4.1でファイル名のprefixやDynamicSQLの記述方法に変更があったため選定は注意
    mybatisGenerator("org.mybatis.generator:mybatis-generator-core:1.4.1")
    mybatisGenerator("mysql:mysql-connector-java:8.0.30")
}
task("mybatisGenerator") {
    doLast {
        ant.withGroovyBuilder {
            "taskdef"("name" to "mbgenerator", "classname" to "org.mybatis.generator.ant.GeneratorAntTask", "classpath" to mybatisGenerator.asPath)
        }
        ant.withGroovyBuilder {
            "mbgenerator"("overwrite" to true, "configfile" to "${rootDir}/db/mybatis/generatorConfig.xml", "verbose" to true)
        }
    }
}

3. Configファイル(XML)を記載

今回はプロジェクト内にdb/mybatisフォルダを作成しgeneratorConfig.xmlという名前で定義します

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 >
    <!-- Kotlinで出力するように設定 -->
    <context id="MySQLTables" targetRuntime="MyBatis3Kotlin">
        <!-- ファイルのDocコメントに日時が書き込まれると毎回Git差分となってしまうためOFF -->
        <commentGenerator>
            <property name="suppressDate" value="true" />
        </commentGenerator>
        <!-- DBに接続情報 -->
        <jdbcConnection
                driverClass="com.mysql.cj.jdbc.Driver"
                connectionURL="jdbc:mysql://localhost:33306/sample?allowPublicKeyRetrieval=true&amp;useSSL=false"
                userId="sample-user"
                password="password" />
        <javaTypeResolver>
            <property name="useJSR310Types" value="true"/>
        </javaTypeResolver>
        <!-- モデルクラスを生成する設定 -->
        <javaModelGenerator
                targetPackage="com.example.db.model"
                targetProject="src/main/kotlin">
            <property name="enableSubPackages" value="true" />
            <property name="trimStrings" value="true" />
        </javaModelGenerator>
        <!-- マッパークラスを生成する設定 -->
        <javaClientGenerator
                targetPackage="com.example.db.mapper"
                targetProject="src/main/kotlin"
                type="XMLMAPPER">
            <property name="enableSubPackages" value="true" />
        </javaClientGenerator>
        <!-- コードを生成するテーブルを指定 -->
        <table tableName="sample_test">
            <!-- PKがオートインクリメント系の場合はInsert後に利用したモデルクラスの変数に自動でPK値が返却されるように設定 -->
            <generatedKey column="sample_test_id" sqlStatement="MySql" identity="true" />
        </table>
    </context>
</generatorConfiguration>

以上で設定は完了です
あとはGradleタスクを実行するとjavaModelGenerator/javaClientGeneratorで指定したパッケージ配下にファイルが自動生成されます  
※指定したパッケージ(ディレクトリ)が存在しない場合、MyBatis Generatorではディレクトリ作成をしてくれないため事前に手動で作っておく必要があります

% ./gradlew mybatisGenerator

MybatisGenerator.png

Kotlin + SpringBootで利用したい場合はこんな感じで利用できます

@Service
class SampleService(
    private val sampleTestMapper: SampleTestMapper
) {
    /**
     * MyBatisGeneratorで生成したモデルとマッパーを利用してテーブルにデータを登録
     * @return Insertで自動採番されたPK
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = [Throwable::class])
    fun registerSample(): Int {
        val record = SampleTest(
            personName = "test",
            sampleStatus = SampleStatus.YET.name,
            registerDatetime = LocalDateTime.now(),
            updateDatetime = LocalDateTime.now()
        )
        sampleTestMapper.insertSelective(record)
        return record.sampleTestId!!
    }
}

まとめ

単一テーブルに対してCRUD操作を行うたびにMapperを作るのはそこそこ大変なので個人的にはMyBatis Generatorを重宝しています  

ただ、MyBatis Generatorで生成されたMapperの特徴としてInsert/Update時に値がNullで設定されている項目はクエリー生成時に除外され更新しないような仕組みになっているためModelクラスに定義されたフィールドはテーブルカラムがNotNull制約であろうとNull許容型になってしまいます
Generatorで生成時にテーブルのNotNull制約を見てModelクラスもNotNullにするプラグインも検討しましたが
・Auto Incrementの値もプログラム側で設定しないといけなくなる
・Update時に更新しなくても良い値がNotNull制約の場合、同じ値を再設定しないといけない
と痒い所に手が届かない感じのため賛否両論あると思います

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1