【MyBatis Generator 1.4.2】Spring Boot3でMyBatis Dynamic SQLを使用する
概要
MyBatis Generatorの公式ページWhat’s New in MyBatis Generatorにあるように、バージョン1.4.0以降ではデフォルトのランタイムがMyBatis Dynamic SQLになりました。
以前このブログに書いたMyBatis Generator 1.3.7の検証記事Spring BootでMyBatis Generator のGradleプラグインを使用するにあるような従来のExampleが廃止され、ラムダを使ってSQLを組み立てる形式となりました。
この記事では以下の環境でMyBatis Generator Dynamic SQLを使う場合の設定を紹介します。
- Spring Boot 3.1.5
- Java 17
- PostgreSQL 15.4
- MyBatis-Generator-Core 1.4.2
build.gradleへ存関係を追加
Spring Bootプロジェクトのbuild.gradleへMyBatis Generatorプラグインと、MyBatis Generator本体であるMyBatis Generator Coreへの依存関係を追加します。
また、Spring BootでMyBatisを使用するときにはMyBatis-Spring-Boot-Starterを使用すると設定が簡単になるのであわせてbuild.gradleへ追加します。
build.gradle中のgeneratorConfig.xmlがMyBatist Generatorの設定ファイルです。Spring Bootプロジェクト内に追加する必要があります。
Gradle Mybatis Generatorプラグインの設定はkimichen13 / mybatis-generator-pluginを参考にしています。
plugins {
id 'java'
id 'org.springframework.boot' version '3.1.5'
id 'io.spring.dependency-management' version '1.1.3'
// MyBatis Generatorプラグイン
id 'com.qqviaja.gradle.MybatisGenerator' version '2.5'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
// MyBatis Generator
mybatisGenerator
}
// MyBatis Generatorの設定
mybatisGenerator {
verbose = true
// 設定ファイルのパス
configFile = "src/main/resources/generatorConfig.xml"
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
// MyBatis
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.2'
implementation group: 'org.mybatis.generator', name: 'mybatis-generator-core', version: '1.4.2'
implementation group: 'org.mybatis.dynamic-sql', name: 'mybatis-dynamic-sql', version: '1.5.0'
implementation group: 'javax.annotation', name: 'javax.annotation-api', version: '1.3.2'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'org.postgresql:postgresql'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:3.0.2'
// MyBatisマッパーのテスト用
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('test') {
useJUnitPlatform()
}
MyBatis Generatorの設定ファイルを作成
generatorConfig.xmlを作成します。このファイルにはMyBatis Generator自体の設定と接続先DB、MyBatis Generatorの実行対象テーブルを記述します。
<?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" >
<!-- 生成される Java ファイルのコメントに日付を付与しない -->
<commentGenerator>
<property name="suppressAllComments" value="true"/>
</commentGenerator>
<!-- 生成元DBの接続情報 -->
<jdbcConnection driverClass="org.postgresql.Driver" connectionURL="jdbc:postgresql://localhost:5432/TestDB"
userId="xxxxx" password="xxxxx"/>
<!-- JSR310の型を使用する -->
<javaTypeResolver>
<property name="useJSR310Types" value="true"/>
</javaTypeResolver>
<!-- 出力先ディレクトリ設定 -->
<javaModelGenerator targetPackage="com.example" targetProject="src/main/java"/>
<javaClientGenerator targetPackage="com.example" targetProject="src/main/java"/>
<table tableName="book">
<!-- 自動生成されるキー -->
<generatedKey column="id" identity="true" sqlStatement="JDBC"/>
<!-- PostgreSQLのtimestamp with timezoneをJavaのOffsetDateTimeへマッピングする -->
<columnOverride column="updated_at" jdbcType="TIMESTAMP_WITH_TIMEZONE" javaType="java.time.OffsetDateTime" />
</table>
</context>
</generatorConfiguration>
動作確認
下記のDDLで作成したbookテーブルに対して上記のgeneratorConfig.xmlの設定でMyBatis Generatorを実行してみます。
create table book (
id serial not null primary key,
book_title text not null,
category_id integer not null,
updated_at timestamp with time zone not null
);
生成されるファイルは以下の3つです。
- Book.java(エンティティクラス)
- BookDynamicSqlSupport.java (テーブル、カラム情報)
- BookMapper.java (マッパークラス)
Book.javaは以下のようになります。
package com.example.demo;
import java.time.OffsetDateTime;
import javax.annotation.Generated;
public class Book {
@Generated("org.mybatis.generator.api.MyBatisGenerator")
private Integer id;
@Generated("org.mybatis.generator.api.MyBatisGenerator")
private String bookTitle;
@Generated("org.mybatis.generator.api.MyBatisGenerator")
private Integer categoryId;
@Generated("org.mybatis.generator.api.MyBatisGenerator")
private OffsetDateTime updatedAt;
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public Integer getId() {
return id;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public void setId(Integer id) {
this.id = id;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public String getBookTitle() {
return bookTitle;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public void setBookTitle(String bookTitle) {
this.bookTitle = bookTitle;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public Integer getCategoryId() {
return categoryId;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public void setCategoryId(Integer categoryId) {
this.categoryId = categoryId;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public OffsetDateTime getUpdatedAt() {
return updatedAt;
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public void setUpdatedAt(OffsetDateTime updatedAt) {
this.updatedAt = updatedAt;
}
}
BookDynamicSqlSupport.javaは以下のようになります。
package com.example.demo;
import java.sql.JDBCType;
import java.time.OffsetDateTime;
import javax.annotation.Generated;
import org.mybatis.dynamic.sql.SqlColumn;
import org.mybatis.dynamic.sql.SqlTable;
public final class BookDynamicSqlSupport {
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public static final Book book = new Book();
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public static final SqlColumn<Integer> id = book.id;
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public static final SqlColumn<String> bookTitle = book.bookTitle;
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public static final SqlColumn<Integer> categoryId = book.categoryId;
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public static final SqlColumn<OffsetDateTime> updatedAt = book.updatedAt;
@Generated("org.mybatis.generator.api.MyBatisGenerator")
public static final class Book extends SqlTable {
public final SqlColumn<Integer> id = column("id", JDBCType.INTEGER);
public final SqlColumn<String> bookTitle = column("book_title", JDBCType.VARCHAR);
public final SqlColumn<Integer> categoryId = column("category_id", JDBCType.INTEGER);
public final SqlColumn<OffsetDateTime> updatedAt = column("updated_at", JDBCType.TIMESTAMP_WITH_TIMEZONE);
public Book() {
super("book");
}
}
}
BookMapper.javaは以下のようになります。
package com.example.demo;
import static com.example.demo.BookDynamicSqlSupport.*;
import static org.mybatis.dynamic.sql.SqlBuilder.*;
import com.example.demo.Book;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import javax.annotation.Generated;
import org.apache.ibatis.annotations.DeleteProvider;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.InsertProvider;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.ResultMap;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.SelectProvider;
import org.apache.ibatis.annotations.UpdateProvider;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.dynamic.sql.BasicColumn;
import org.mybatis.dynamic.sql.delete.DeleteDSLCompleter;
import org.mybatis.dynamic.sql.delete.render.DeleteStatementProvider;
import org.mybatis.dynamic.sql.insert.render.InsertStatementProvider;
import org.mybatis.dynamic.sql.insert.render.MultiRowInsertStatementProvider;
import org.mybatis.dynamic.sql.select.CountDSLCompleter;
import org.mybatis.dynamic.sql.select.SelectDSLCompleter;
import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;
import org.mybatis.dynamic.sql.update.UpdateDSL;
import org.mybatis.dynamic.sql.update.UpdateDSLCompleter;
import org.mybatis.dynamic.sql.update.UpdateModel;
import org.mybatis.dynamic.sql.update.render.UpdateStatementProvider;
import org.mybatis.dynamic.sql.util.SqlProviderAdapter;
import org.mybatis.dynamic.sql.util.mybatis3.MyBatis3Utils;
@Mapper
public interface BookMapper {
@Generated("org.mybatis.generator.api.MyBatisGenerator")
BasicColumn[] selectList = BasicColumn.columnList(id, bookTitle, categoryId, updatedAt);
@Generated("org.mybatis.generator.api.MyBatisGenerator")
@SelectProvider(type=SqlProviderAdapter.class, method="select")
long count(SelectStatementProvider selectStatement);
@Generated("org.mybatis.generator.api.MyBatisGenerator")
@DeleteProvider(type=SqlProviderAdapter.class, method="delete")
int delete(DeleteStatementProvider deleteStatement);
@Generated("org.mybatis.generator.api.MyBatisGenerator")
@InsertProvider(type=SqlProviderAdapter.class, method="insert")
@Options(useGeneratedKeys=true,keyProperty="record.id")
int insert(InsertStatementProvider<Book> insertStatement);
@Generated("org.mybatis.generator.api.MyBatisGenerator")
@Insert({
"${insertStatement}"
})
@Options(useGeneratedKeys=true,keyProperty="records.id")
int insertMultiple(@Param("insertStatement") String insertStatement, @Param("records") List<Book> records);
@Generated("org.mybatis.generator.api.MyBatisGenerator")
default int insertMultiple(MultiRowInsertStatementProvider<Book> multipleInsertStatement) {
return insertMultiple(multipleInsertStatement.getInsertStatement(), multipleInsertStatement.getRecords());
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
@SelectProvider(type=SqlProviderAdapter.class, method="select")
@ResultMap("BookResult")
Optional<Book> selectOne(SelectStatementProvider selectStatement);
@Generated("org.mybatis.generator.api.MyBatisGenerator")
@SelectProvider(type=SqlProviderAdapter.class, method="select")
@Results(id="BookResult", value = {
@Result(column="id", property="id", jdbcType=JdbcType.INTEGER, id=true),
@Result(column="book_title", property="bookTitle", jdbcType=JdbcType.VARCHAR),
@Result(column="category_id", property="categoryId", jdbcType=JdbcType.INTEGER),
@Result(column="updated_at", property="updatedAt", jdbcType=JdbcType.TIMESTAMP_WITH_TIMEZONE)
})
List<Book> selectMany(SelectStatementProvider selectStatement);
@Generated("org.mybatis.generator.api.MyBatisGenerator")
@UpdateProvider(type=SqlProviderAdapter.class, method="update")
int update(UpdateStatementProvider updateStatement);
@Generated("org.mybatis.generator.api.MyBatisGenerator")
default long count(CountDSLCompleter completer) {
return MyBatis3Utils.countFrom(this::count, book, completer);
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
default int delete(DeleteDSLCompleter completer) {
return MyBatis3Utils.deleteFrom(this::delete, book, completer);
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
default int deleteByPrimaryKey(Integer id_) {
return delete(c ->
c.where(id, isEqualTo(id_))
);
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
default int insert(Book record) {
return MyBatis3Utils.insert(this::insert, record, book, c ->
c.map(bookTitle).toProperty("bookTitle")
.map(categoryId).toProperty("categoryId")
.map(updatedAt).toProperty("updatedAt")
);
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
default int insertMultiple(Collection<Book> records) {
return MyBatis3Utils.insertMultiple(this::insertMultiple, records, book, c ->
c.map(bookTitle).toProperty("bookTitle")
.map(categoryId).toProperty("categoryId")
.map(updatedAt).toProperty("updatedAt")
);
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
default int insertSelective(Book record) {
return MyBatis3Utils.insert(this::insert, record, book, c ->
c.map(bookTitle).toPropertyWhenPresent("bookTitle", record::getBookTitle)
.map(categoryId).toPropertyWhenPresent("categoryId", record::getCategoryId)
.map(updatedAt).toPropertyWhenPresent("updatedAt", record::getUpdatedAt)
);
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
default Optional<Book> selectOne(SelectDSLCompleter completer) {
return MyBatis3Utils.selectOne(this::selectOne, selectList, book, completer);
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
default List<Book> select(SelectDSLCompleter completer) {
return MyBatis3Utils.selectList(this::selectMany, selectList, book, completer);
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
default List<Book> selectDistinct(SelectDSLCompleter completer) {
return MyBatis3Utils.selectDistinct(this::selectMany, selectList, book, completer);
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
default Optional<Book> selectByPrimaryKey(Integer id_) {
return selectOne(c ->
c.where(id, isEqualTo(id_))
);
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
default int update(UpdateDSLCompleter completer) {
return MyBatis3Utils.update(this::update, book, completer);
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
static UpdateDSL<UpdateModel> updateAllColumns(Book record, UpdateDSL<UpdateModel> dsl) {
return dsl.set(bookTitle).equalTo(record::getBookTitle)
.set(categoryId).equalTo(record::getCategoryId)
.set(updatedAt).equalTo(record::getUpdatedAt);
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
static UpdateDSL<UpdateModel> updateSelectiveColumns(Book record, UpdateDSL<UpdateModel> dsl) {
return dsl.set(bookTitle).equalToWhenPresent(record::getBookTitle)
.set(categoryId).equalToWhenPresent(record::getCategoryId)
.set(updatedAt).equalToWhenPresent(record::getUpdatedAt);
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
default int updateByPrimaryKey(Book record) {
return update(c ->
c.set(bookTitle).equalTo(record::getBookTitle)
.set(categoryId).equalTo(record::getCategoryId)
.set(updatedAt).equalTo(record::getUpdatedAt)
.where(id, isEqualTo(record::getId))
);
}
@Generated("org.mybatis.generator.api.MyBatisGenerator")
default int updateByPrimaryKeySelective(Book record) {
return update(c ->
c.set(bookTitle).equalToWhenPresent(record::getBookTitle)
.set(categoryId).equalToWhenPresent(record::getCategoryId)
.set(updatedAt).equalToWhenPresent(record::getUpdatedAt)
.where(id, isEqualTo(record::getId))
);
}
}
MyBatisマッパーの単体テストの作成方法は以下の記事にまとめています。