MyBatis Generatorの新しいデフォルトランタイムMyBatis3DynamicSqlをJavaから実行してみる

# はじめに
[What's New in MyBatis Generator](https://mybatis.org/generator/whatsNew.html)にあるように、`mybatis-generator-core:1.4.1`からは`MyBatis3DynamicSQL`というランタイムがデフォルトになった。

> This release is primarily focused on updating the runtimes for MyBatis Dynamic SQL ("MyBatis3DynamicSQL" and "MyBatis3Kotlin"). The generated code for "MyBatis3DynamicSQL" is now dependent on MyBatis Dynamic SQL version 1.3.1 or later. The generated code for "MyBatis3Kotlin" is now dependent on MyBatis Dynamic SQL version 1.4.0 or later. See below for details about these changes. See the GitHub page for milestone 1.4.1 for details other changes in this release: Milestone 1.4.1.

[Target Runtime Information and Samples](https://mybatis.org/generator/quickstart.html#target-runtime-information-and-samples)によると、`MyBatis3DynamicSQL`の主な特徴は以下。
- Javaコードを生成する
- XMLは生成しない（←ここ重要）
- 生成されるJavaコードは`org.mybatis.dynamic-sql:mybatis-dynamic-sql`ライブラリの依存関係が必要

ついにXMLを脱却したとのことなので、実際に生成してみる。

# build.gradle
build.gradleの`dependencies`に以下を追記する
```gradle:build.gradle(抜粋)
dependencies {
...
	implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.2'
	testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:2.2.2'
	implementation 'org.mybatis.generator:mybatis-generator-core:1.4.1'
	implementation 'org.mybatis.dynamic-sql:mybatis-dynamic-sql:1.4.0'
...
}
```

# generatorConfig.xml
`src/main/resources`に`generatorConfig.xml`を作成する。
※DBの情報は適宜置換してください

```xml:generatorCinfig.xml
<!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="mbSpringSecurityDemo" targetRuntime="MyBatis3DynamicSql">
    <commentGenerator>
      <property name="suppressAllComments" value="true"/>
    </commentGenerator>
    <jdbcConnection driverClass="org.postgresql.Driver"
                    connectionURL="jdbc:postgresql://localhost:5432/[db名]"
                    userId="[user]"
                    password="[password]" />
    <!-- PostgreSQLのTIMESTAMP型をJavaのLocalDateTimeへマッピングする  -->
    <javaTypeResolver>
      <property name="useJSR310Types" value="true"/>
    </javaTypeResolver>
    <javaModelGenerator targetPackage="com.example.springsecuritydemo.mbgenerate.entity" targetProject="src/main/java" />
    <javaClientGenerator targetPackage="com.example.springsecuritydemo.mbgenerate.crud"
                         targetProject="src/main/java" type="ANNOTATEDMAPPER" />

    <table schema="public" tableName="%" />
  </context>
</generatorConfiguration>
```

## commentGenerator
生成されるjavaファイルのコメントに記載される生成日時を省略するかどうか。

## javaModelGenerator
生成されるjavaファイル(entity)の出力先。
`[Table名].java`のフォーマットで出力される。

## javaClientGenerator
生成されるjavaファイル(**Mapaer.java, **DynamicSqlSupport.java)の出力先。
※これがないとentityだけ生成され、**Mapaer.java, **DynamicSqlSupport.javaが生成されない。

## 公式DOC
その他、xmlの詳細な仕様は以下

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

## その他参考ページ

https://mybatis.org/generator/quickstart.html

https://sebenkyo.com/2022/05/06/post-2445/

# MyBatis Generatorを実行するJavaファイル
[Running MyBatis Generator With Java](https://mybatis.org/generator/running/runningWithJava.html)によれば、JavaからMyBatis Generatorを実行できるとのことなので、やってみる。

```java:MyBatisGeneratorExecutor
package com.example.springsecuritydemo;

import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.internal.DefaultShellCallback;

import java.io.File;
import java.util.ArrayList;

/**
 * 参考：https://mybatis.org/generator/running/runningWithJava.html
 */
public class MyBatisGeneratorExecutor {
    public static void main(final String[] args) throws Exception {
        final var warnings = new ArrayList<String>();
        final var configuration = new ConfigurationParser(warnings)
                .parseConfiguration(new File("src/main/resources/generatorConfig.xml"));
        final var defaultShellCallback = new DefaultShellCallback(true);

        final var myBatisGenerator = new MyBatisGenerator(configuration, defaultShellCallback, warnings);
        myBatisGenerator.generate(null);
    }
}
```

↑をIDEやCLIから実行すると、
- [Table名].java
- [Table名]Mapper.java
- [Table名]DynamicSqlSupport.java

の3種類のファイルがテーブルごとに生成される。

## 参考
https://mybatis.org/generator/running/runningWithJava.html
↑の丸々コピペでもOK

# 生成されるファイル例

例：user_roleというテーブルがあった場合、
- UserRole.java
- UserRoleMapper.java
- UserRoleDynamicSqlSupport.java

の3種類が生成される。

```sql
-- ユーザー
CREATE TABLE login_user(
 id INTEGER PRIMARY KEY, -- ユーザーのID
 name VARCHAR(128) NOT NULL, -- ユーザーの表示名
 email VARCHAR(256) NOT NULL, -- メールアドレス（ログイン時に利用）
 password VARCHAR(128) NOT NULL -- ハッシュ化済みのパスワード
);
```

テーブルの参考元：

https://www.docswell.com/s/MasatoshiTada/KGVY9K-spring-security-intro


```java:LoginUser.java
package com.example.springsecuritydemo.mbgenerate.entity;

import javax.annotation.Generated;

public class LoginUser {
    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    private Integer id;

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    private String name;

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    private String email;

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    private String password;

    @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 getName() {
        return name;
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    public void setName(String name) {
        this.name = name;
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    public String getEmail() {
        return email;
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    public void setEmail(String email) {
        this.email = email;
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    public String getPassword() {
        return password;
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    public void setPassword(String password) {
        this.password = password;
    }
}
```

```java:LoginUserMapper.java
package com.example.springsecuritydemo.mbgenerate.crud;

import static com.example.springsecuritydemo.mbgenerate.crud.LoginUserDynamicSqlSupport.*;
import static org.mybatis.dynamic.sql.SqlBuilder.isEqualTo;

import com.example.springsecuritydemo.mbgenerate.entity.LoginUser;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import javax.annotation.Generated;
import org.apache.ibatis.annotations.Mapper;
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.type.JdbcType;
import org.mybatis.dynamic.sql.BasicColumn;
import org.mybatis.dynamic.sql.delete.DeleteDSLCompleter;
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.util.SqlProviderAdapter;
import org.mybatis.dynamic.sql.util.mybatis3.CommonCountMapper;
import org.mybatis.dynamic.sql.util.mybatis3.CommonDeleteMapper;
import org.mybatis.dynamic.sql.util.mybatis3.CommonInsertMapper;
import org.mybatis.dynamic.sql.util.mybatis3.CommonUpdateMapper;
import org.mybatis.dynamic.sql.util.mybatis3.MyBatis3Utils;

@Mapper
public interface LoginUserMapper extends CommonCountMapper, CommonDeleteMapper, CommonInsertMapper<LoginUser>, CommonUpdateMapper {
    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    BasicColumn[] selectList = BasicColumn.columnList(id, name, email, password);

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    @SelectProvider(type=SqlProviderAdapter.class, method="select")
    @Results(id="LoginUserResult", value = {
        @Result(column="id", property="id", jdbcType=JdbcType.INTEGER, id=true),
        @Result(column="name", property="name", jdbcType=JdbcType.VARCHAR),
        @Result(column="email", property="email", jdbcType=JdbcType.VARCHAR),
        @Result(column="password", property="password", jdbcType=JdbcType.VARCHAR)
    })
    List<LoginUser> selectMany(SelectStatementProvider selectStatement);

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    @SelectProvider(type=SqlProviderAdapter.class, method="select")
    @ResultMap("LoginUserResult")
    Optional<LoginUser> selectOne(SelectStatementProvider selectStatement);

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    default long count(CountDSLCompleter completer) {
        return MyBatis3Utils.countFrom(this::count, loginUser, completer);
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    default int delete(DeleteDSLCompleter completer) {
        return MyBatis3Utils.deleteFrom(this::delete, loginUser, 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(LoginUser row) {
        return MyBatis3Utils.insert(this::insert, row, loginUser, c ->
            c.map(id).toProperty("id")
            .map(name).toProperty("name")
            .map(email).toProperty("email")
            .map(password).toProperty("password")
        );
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    default int insertMultiple(Collection<LoginUser> records) {
        return MyBatis3Utils.insertMultiple(this::insertMultiple, records, loginUser, c ->
            c.map(id).toProperty("id")
            .map(name).toProperty("name")
            .map(email).toProperty("email")
            .map(password).toProperty("password")
        );
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    default int insertSelective(LoginUser row) {
        return MyBatis3Utils.insert(this::insert, row, loginUser, c ->
            c.map(id).toPropertyWhenPresent("id", row::getId)
            .map(name).toPropertyWhenPresent("name", row::getName)
            .map(email).toPropertyWhenPresent("email", row::getEmail)
            .map(password).toPropertyWhenPresent("password", row::getPassword)
        );
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    default Optional<LoginUser> selectOne(SelectDSLCompleter completer) {
        return MyBatis3Utils.selectOne(this::selectOne, selectList, loginUser, completer);
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    default List<LoginUser> select(SelectDSLCompleter completer) {
        return MyBatis3Utils.selectList(this::selectMany, selectList, loginUser, completer);
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    default List<LoginUser> selectDistinct(SelectDSLCompleter completer) {
        return MyBatis3Utils.selectDistinct(this::selectMany, selectList, loginUser, completer);
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    default Optional<LoginUser> 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, loginUser, completer);
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    static UpdateDSL<UpdateModel> updateAllColumns(LoginUser row, UpdateDSL<UpdateModel> dsl) {
        return dsl.set(id).equalTo(row::getId)
                .set(name).equalTo(row::getName)
                .set(email).equalTo(row::getEmail)
                .set(password).equalTo(row::getPassword);
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    static UpdateDSL<UpdateModel> updateSelectiveColumns(LoginUser row, UpdateDSL<UpdateModel> dsl) {
        return dsl.set(id).equalToWhenPresent(row::getId)
                .set(name).equalToWhenPresent(row::getName)
                .set(email).equalToWhenPresent(row::getEmail)
                .set(password).equalToWhenPresent(row::getPassword);
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    default int updateByPrimaryKey(LoginUser row) {
        return update(c ->
            c.set(name).equalTo(row::getName)
            .set(email).equalTo(row::getEmail)
            .set(password).equalTo(row::getPassword)
            .where(id, isEqualTo(row::getId))
        );
    }

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    default int updateByPrimaryKeySelective(LoginUser row) {
        return update(c ->
            c.set(name).equalToWhenPresent(row::getName)
            .set(email).equalToWhenPresent(row::getEmail)
            .set(password).equalToWhenPresent(row::getPassword)
            .where(id, isEqualTo(row::getId))
        );
    }
}
```

```java:LoginUserDynamicSqlSupport.java
package com.example.springsecuritydemo.mbgenerate.crud;

import org.mybatis.dynamic.sql.AliasableSqlTable;
import org.mybatis.dynamic.sql.SqlColumn;

import javax.annotation.Generated;
import java.sql.JDBCType;

public final class LoginUserDynamicSqlSupport {
    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    public static final LoginUser loginUser = new LoginUser();

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    public static final SqlColumn<Integer> id = loginUser.id;

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    public static final SqlColumn<String> name = loginUser.name;

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    public static final SqlColumn<String> email = loginUser.email;

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    public static final SqlColumn<String> password = loginUser.password;

    @Generated("org.mybatis.generator.api.MyBatisGenerator")
    public static final class LoginUser extends AliasableSqlTable<LoginUser> {
        public final SqlColumn<Integer> id = column("id", JDBCType.INTEGER);

        public final SqlColumn<String> name = column("name", JDBCType.VARCHAR);

        public final SqlColumn<String> email = column("email", JDBCType.VARCHAR);

        public final SqlColumn<String> password = column("password", JDBCType.VARCHAR);

        public LoginUser() {
            super("public.login_user", LoginUser::new);
        }
    }
}
```

# 動的SQLの利用
[MyBatis Dynamic SQL Usage Notes](https://mybatis.org/generator/generatedobjects/dynamicSql.html)を参考にやってみる。
```java:LoginUserRepository.java(抜粋)
    private final LoginUserMapper loginUserMapper;

    ...

    public Optional<LoginUser> findById(final int id) {
        return loginUserMapper.selectByPrimaryKey(id);
    }

    public void simpleWhere() {
        log.info("----- simple where -----");
        loginUserMapper.select(c -> c.where(LoginUserDynamicSqlSupport.id, SqlBuilder.isEqualTo(1)))
                .forEach(e -> log.info("{}, {}, {}, {}", e.getId(), e.getName(), e.getEmail(), e.getPassword()));
    }

    public void complexWhere() {
        log.info("----- complex where -----");
        loginUserMapper.select(c -> c.where(LoginUserDynamicSqlSupport.email, SqlBuilder.isLike("%@example.com"))
                .and(LoginUserDynamicSqlSupport.name, SqlBuilder.isLike("管理%"))
        ).forEach(e -> log.info("{}, {}, {}, {}", e.getId(), e.getName(), e.getEmail(), e.getPassword()));
    }
```

```sql
INSERT INTO login_user(id, name, email, password) VALUES(1, '一般太郎', 'general@example.com', '$2a$10$6fPXYK.C9rCWUBifuqBIB.GRNU.nQtBpdzkkKis8ETaKVKxNo/ltO');
INSERT INTO login_user(id, name, email, password) VALUES(2, '管理太郎', 'admin@example.com', '$2a$10$SJTWvNl16fCU7DaXtWC0DeN/A8IOakpCkWWNZ/FKRV2CHvWElQwMS');
```

```
2022-10-13 08:05:09.138  INFO 75631 --- [nio-8080-exec-1] c.e.s.repository.LoginUserRepository     : ----- simple where -----
2022-10-13 08:05:09.494 DEBUG 75631 --- [nio-8080-exec-1] c.e.s.m.crud.LoginUserMapper.selectMany  : ==>  Preparing: select id, name, email, password from public.login_user where id = ?
2022-10-13 08:05:09.507 DEBUG 75631 --- [nio-8080-exec-1] c.e.s.m.crud.LoginUserMapper.selectMany  : ==> Parameters: 1(Integer)
2022-10-13 08:05:09.537 DEBUG 75631 --- [nio-8080-exec-1] c.e.s.m.crud.LoginUserMapper.selectMany  : <==      Total: 1
2022-10-13 08:05:09.543  INFO 75631 --- [nio-8080-exec-1] c.e.s.repository.LoginUserRepository     : 1, 一般太郎, general@example.com, $2a$10$6fPXYK.C9rCWUBifuqBIB.GRNU.nQtBpdzkkKis8ETaKVKxNo/ltO
2022-10-13 08:05:09.549  INFO 75631 --- [nio-8080-exec-1] c.e.s.repository.LoginUserRepository     : ----- complex where -----
2022-10-13 08:05:09.552 DEBUG 75631 --- [nio-8080-exec-1] c.e.s.m.crud.LoginUserMapper.selectMany  : ==>  Preparing: select id, name, email, password from public.login_user where email like ? and name like ?
2022-10-13 08:05:09.553 DEBUG 75631 --- [nio-8080-exec-1] c.e.s.m.crud.LoginUserMapper.selectMany  : ==> Parameters: %@example.com(String), 管理%(String)
2022-10-13 08:05:09.557 DEBUG 75631 --- [nio-8080-exec-1] c.e.s.m.crud.LoginUserMapper.selectMany  : <==      Total: 1
2022-10-13 08:05:09.557  INFO 75631 --- [nio-8080-exec-1] c.e.s.repository.LoginUserRepository     : 2, 管理太郎, admin@example.com, $2a$10$ux77EHnIhRnwQfqX8AX0WORiOFV9e2fQPmDL2nA9ypDNVL9BzYU5K

```

`LoginUserMapper`からラムダでつないでwhere句を記述できる。
ORDER BYやJOINなどもできるもよう。

※JPAに似てるなーと個人的に感じる。

# native SQLを実行したい
MyBatis Dynamic SQLはかなり強力だが、それでもやっぱり生のnativeなSQLを書いて実行したいときもある。

色々調べたがあまり情報がなく、自分の結論としては`MyBatis Thymeleaf`を使って*.sqlファイルを実行するやり方に落ち着いた。

## mybatis-thymeleafの導入
[org.mybatis.scripting:mybatis-thymeleaf](https://mvnrepository.com/artifact/org.mybatis.scripting/mybatis-thymeleaf)をbuild.gradleに記載する。

※脆弱性がいくつか上がっているが、postgresqlは関係なさそうなので一旦無視。。。

```gradle:build.gradle(抜粋)
dependencies {
...
	implementation 'org.mybatis.scripting:mybatis-thymeleaf:1.0.3'
...
}
```

## Custom***Mapper.javaの作成
前述した`LoginUserMapper.java`を継承した`CustomLoginUserMapper.java`を作成する。

```java:CustomLoginUserMapper.java
package com.example.springsecuritydemo.mbgenerate.crud;

import com.example.springsecuritydemo.mbgenerate.entity.LoginUser;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

@Mapper
public interface CustomLoginUserMapper extends LoginUserMapper {
    @Select("SELECT * FROM login_user;")
    List<LoginUser> selectAll();

    @Select("/mbgenerate/crud/CustomLoginUserMapper/selectAll.sql")
    List<LoginUser> selectAllBySqlFile();
}
```

`@Select("SELECT * FROM login_user;")`のようにSQLをソースにベタ書きしても実行はできる。（けど、非推奨かな。。）
`@Select("/mbgenerate/crud/CustomLoginUserMapper/selectAll.sql")`は`src/main/resources/mbgenerate/crud/CustomLoginUserMapper/selectAll.sql`を作成し、そこにSQLを書いている。

```sql:selectAll.sql
SELECT * FROM login_user;
```

これらを先程の`LoginUserRepository.java`から実行してみる。
```java:LoginUserRepository.java(抜粋)
    private final CustomLoginUserMapper customLoginUserMapper;

    ...
    
    public void useCustomLoginUserMapper() {
        log.info("----- use custom mapper -----");
        customLoginUserMapper.selectAll()
                .forEach(e -> log.info("{}, {}, {}, {}", e.getId(), e.getName(), e.getEmail(), e.getPassword()));
        customLoginUserMapper.selectAllBySqlFile()
                .forEach(e -> log.info("{}, {}, {}, {}", e.getId(), e.getName(), e.getEmail(), e.getPassword()));
    }
```

```sql
INSERT INTO login_user(id, name, email, password) VALUES(1, '一般太郎', 'general@example.com', '$2a$10$6fPXYK.C9rCWUBifuqBIB.GRNU.nQtBpdzkkKis8ETaKVKxNo/ltO');
INSERT INTO login_user(id, name, email, password) VALUES(2, '管理太郎', 'admin@example.com', '$2a$10$SJTWvNl16fCU7DaXtWC0DeN/A8IOakpCkWWNZ/FKRV2CHvWElQwMS');
```

```
2022-10-13 08:05:09.557  INFO 75631 --- [nio-8080-exec-1] c.e.s.repository.LoginUserRepository     : ----- use custom mapper -----
2022-10-13 08:05:09.559 DEBUG 75631 --- [nio-8080-exec-1] c.e.s.m.c.C.selectAll                    : ==>  Preparing: SELECT * FROM login_user;
2022-10-13 08:05:09.559 DEBUG 75631 --- [nio-8080-exec-1] c.e.s.m.c.C.selectAll                    : ==> Parameters: 
2022-10-13 08:05:09.560 DEBUG 75631 --- [nio-8080-exec-1] c.e.s.m.c.C.selectAll                    : <==      Total: 2
2022-10-13 08:05:09.561  INFO 75631 --- [nio-8080-exec-1] c.e.s.repository.LoginUserRepository     : 1, 一般太郎, general@example.com, $2a$10$6fPXYK.C9rCWUBifuqBIB.GRNU.nQtBpdzkkKis8ETaKVKxNo/ltO
2022-10-13 08:05:09.561  INFO 75631 --- [nio-8080-exec-1] c.e.s.repository.LoginUserRepository     : 2, 管理太郎, admin@example.com, $2a$10$ux77EHnIhRnwQfqX8AX0WORiOFV9e2fQPmDL2nA9ypDNVL9BzYU5K
2022-10-13 08:05:09.563 DEBUG 75631 --- [nio-8080-exec-1] c.e.s.m.c.C.selectAllBySqlFile           : ==>  Preparing: SELECT * FROM login_user;
2022-10-13 08:05:09.563 DEBUG 75631 --- [nio-8080-exec-1] c.e.s.m.c.C.selectAllBySqlFile           : ==> Parameters: 
2022-10-13 08:05:09.565 DEBUG 75631 --- [nio-8080-exec-1] c.e.s.m.c.C.selectAllBySqlFile           : <==      Total: 2
2022-10-13 08:05:09.565  INFO 75631 --- [nio-8080-exec-1] c.e.s.repository.LoginUserRepository     : 1, 一般太郎, general@example.com, $2a$10$6fPXYK.C9rCWUBifuqBIB.GRNU.nQtBpdzkkKis8ETaKVKxNo/ltO
2022-10-13 08:05:09.568  INFO 75631 --- [nio-8080-exec-1] c.e.s.repository.LoginUserRepository     : 2, 管理太郎, admin@example.com, $2a$10$ux77EHnIhRnwQfqX8AX0WORiOFV9e2fQPmDL2nA9ypDNVL9BzYU5K

```

# おまけ　logging
MyBatisが実行したSQLのログを出力する際は、`application.yml`を編集するだけでOK。
（ログ出力機能は`org.mybatis.spring.boot:mybatis-spring-boot-starter`に内蔵されている）
具体的には、***Mapper.javaがあるパッケージのログレベルを`DEBUG`にする。
```yml:application.yml(抜粋)
logging:
  level:
    com:
      example:
        springsecuritydemo: DEBUG
```

参考：

https://ito-u-oti.com/post-1064/