はじめに
What's New in MyBatis Generatorにあるように、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によると、MyBatis3DynamicSQL
の主な特徴は以下。
- Javaコードを生成する
- XMLは生成しない(←ここ重要)
- 生成されるJavaコードは
org.mybatis.dynamic-sql:mybatis-dynamic-sql
ライブラリの依存関係が必要
ついにXMLを脱却したとのことなので、実際に生成してみる。
build.gradle
build.gradleのdependencies
に以下を追記する
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の情報は適宜置換してください
<!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の詳細な仕様は以下
その他参考ページ
MyBatis Generatorを実行するJavaファイル
Running MyBatis Generator With Javaによれば、JavaからMyBatis Generatorを実行できるとのことなので、やってみる。
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種類が生成される。
-- ユーザー
CREATE TABLE login_user(
id INTEGER PRIMARY KEY, -- ユーザーのID
name VARCHAR(128) NOT NULL, -- ユーザーの表示名
email VARCHAR(256) NOT NULL, -- メールアドレス(ログイン時に利用)
password VARCHAR(128) NOT NULL -- ハッシュ化済みのパスワード
);
テーブルの参考元:
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;
}
}
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))
);
}
}
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を参考にやってみる。
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()));
}
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をbuild.gradleに記載する。
※脆弱性がいくつか上がっているが、postgresqlは関係なさそうなので一旦無視。。。
dependencies {
...
implementation 'org.mybatis.scripting:mybatis-thymeleaf:1.0.3'
...
}
Custom***Mapper.javaの作成
前述したLoginUserMapper.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を書いている。
SELECT * FROM login_user;
これらを先程の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()));
}
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
にする。
logging:
level:
com:
example:
springsecuritydemo: DEBUG
参考: