MyBatis とは
SQL と Java オブジェクトを紐付ける永続化フレームワーク。
以前は iBATIS という名前で Apache プロジェクトの1つとして開発されていた。
しかし、 2010年6月に Apache ソフトウェア財団での開発が中止され、現在は MyBatis という名前で開発されている。
SQL 文を完全にコントロールしたい場合に使いやすいらしい。
環境
OS
Windows 7 64bit
Java
1.8.0_65
データベース
MySQL 5.5.28
Hello World
インストール
compile 'org.mybatis:mybatis:3.3.0'
compile 'mysql:mysql-connector-java:5.1.38'
データベースの準備
フォルダ構成
|-src/main/
| |-java/
| | `-sample/mybatis/
| | `-Main.java
| `-resources/
| |-mybatis-config.xml
| `-sample_mapper.xml
`-build.gradle
設定ファイル
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="sample_id">
<environment id="sample_id">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost/test"/>
<property name="username" value="test"/>
<property name="password" value="test"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="sample_mapper.xml"/>
</mappers>
</configuration>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<select id="selectTest" resultType="map">
select * from test_table
</select>
</mapper>
実装
package sample.mybatis;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Main {
public static void main(String[] args) throws Exception {
// ★ルートとなる設定ファイルを読み込む
try (InputStream in = Main.class.getResourceAsStream("/mybatis-config.xml")) {
// ★設定ファイルを元に、 SqlSessionFactory を作成する
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// ★SqlSessionFactory から SqlSession を生成する
try (SqlSession session = factory.openSession()) {
// ★SqlSession を使って SQL を実行する
List<Map<String, Object>> result = session.selectList("sample.mybatis.selectTest");
result.forEach(row -> {
System.out.println("---------------");
row.forEach((columnName, value) -> {
System.out.printf("columnName=%s, value=%s%n", columnName, value);
});
});
}
}
}
}
---------------
columnName=id, value=1
columnName=value, value=hoge
---------------
columnName=id, value=2
columnName=value, value=fuga
---------------
columnName=id, value=3
columnName=value, value=piyo
説明
- 設定ファイル
- まず、データベースとの接続情報などを定義したメインとなる設定ファイルを用意する(
mybatis-config.xml
)。 - 次に、 SQL 文や検索結果のマッピング方法などを定義したファイルを用意する(
sample_mapper.xml
)。
- まず、データベースとの接続情報などを定義したメインとなる設定ファイルを用意する(
- 実装
- まず、
SqlSessionFactoryBuilder
を使って、設定ファイル(mybatis-config.xml
)を読み込む。 -
SqlSessionFactory
を使って、SqlSession
を生成する。 -
SqlSession
に用意されている SQL 実行用のメソッドを使って SQL を実行する(selectList()
)。 - 第一引数には、実行する SQL を識別するための ID (ステートメントID)を指定する。
- ステートメント ID や戻り値の型は、
sample_mapper.xml
で定義された情報が元になっている。
- まず、
各インスタンスの推奨スコープ
インスタンス | 説明 | 推奨スコープ |
---|---|---|
SqlSessionFactoryBuilder |
SqlSessionFactory を作ったら用済み。 |
メソッドスコープ |
SqlSessionFactory |
一度作ったら、それを使い回すべき。 | アプリケーションスコープ |
SqlSession |
非スレッドセーフ。一連の SQL 実行が終わったらクローズする。 | リクエストスコープ |
Mapper インスタンス※ |
SqlSession と同調すべきだが、より短いスコープが良い。 |
メソッドスコープ |
※Mapper については こちら を参照。
- メソッドスコープ
- メソッド内だけで使い捨てる。
- リクエストスコープ
- Web アプリで、1つのリクエストの間だけ同じインスタンスを使い回す。
- 他のリクエストからは参照させないようにする。
- アプリケーションスコープ
- アプリケーションが動いている間ずっと同じインスタンスを使い回す。
設定ファイル
外部のプロパティファイルを読み込む
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="hoge.properties">
<property name="hoge" value="property tag"/>
<property name="fuga" value="property tag"/>
<property name="piyo" value="property tag"/>
</properties>
</configuration>
fuga=property file
piyo=property file
package sample.mybatis;
import java.io.InputStream;
import java.util.Properties;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Main {
public static void main(String[] args) throws Exception {
try (InputStream in = Main.class.getResourceAsStream("/mybatis-config.xml")) {
Properties prop = new Properties();
prop.put("piyo", "Properties Class");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in, prop);
Properties properties = factory.getConfiguration().getVariables();
properties.forEach((key, value) -> {
System.out.printf("%s=%s%n", key, value);
});
}
}
}
hoge=property tag
fuga=property file
piyo=Properties Class
- 以下の場所でプロパティを定義できる。
-
<properties>
タグ内の<property>
タグ。 -
<properties>
タグで読み込んだプロパティファイル。 -
SqlSessionFactory
を生成するときにProperties
インスタンスを渡す。
-
- 各定義は上述の順序で読み込まれ、後で読み込まれたもので上書きされる。
-
<properties>
タグで別ファイルを読み込む場合は、resource
属性でリソースファイルを、url
属性で URL 指定でファイルを読み込める。-
url
属性で指定する場合はfile:///C:/foo/bar/hoge.properties
みたいな感じで指定する。
-
- 読み込んだプロパティは、
${...}
という形で参照できる。
複数のデータベース接続を定義する
データベースの接続先情報は、 environment
という単位で定義する。
SqlSessionFactory
は、この environment
単位でインスタンスを生成することになる。
デフォルトでは <environments>
タグの default
属性で指定した environment
が使用される。
しかし、 SqlSessionFactory
を生成するときに任意の environment
を指定することもできる。
test_table @ hoge スキーマ
test_table @ fuga スキーマ
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="hoge"> <!-- ★hoge をデフォルト指定 -->
<environment id="hoge">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost/hoge"/>
<property name="username" value="test"/>
<property name="password" value="test"/>
</dataSource>
</environment>
<environment id="fuga">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost/fuga"/>
<property name="username" value="test"/>
<property name="password" value="test"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="sample_mapper.xml"/>
</mappers>
</configuration>
package sample.mybatis;
import java.io.InputStream;
import java.util.function.Function;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Main {
public static void main(String[] args) throws Exception {
execute(in -> new SqlSessionFactoryBuilder().build(in)); // ★environment を指定しない
execute(in -> new SqlSessionFactoryBuilder().build(in, "fuga")); // ★environment を指定
}
private static void execute(Function<InputStream, SqlSessionFactory> sqlSessionFactorySupplier) throws Exception {
try (InputStream in = Main.class.getResourceAsStream("/mybatis-config.xml")) {
SqlSessionFactory factory = sqlSessionFactorySupplier.apply(in);
try (SqlSession session = factory.openSession()) {
System.out.println(session.selectList("sample.mybatis.selectTest"));
}
}
System.out.println();
}
}
[{id=1, value=fizz}, {id=2, value=buzz}]
[{id=1, value=hoge}, {id=2, value=fuga}, {id=3, value=piyo}]
-
SqlSessionFactoryBuilder#build()
の第二引数で、environment
を指定することができる。
Java EE サーバーに管理されたデータソースを使用する
Java EE サーバー上で動かす場合、データソースはサーバーに登録しておいて、トランザクション制御は Java EE コンテナに任せることができる。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="test">
<environment id="test">
<transactionManager type="MANAGED"/> <!-- ★type を MANAGED にする -->
<dataSource type="JNDI"> <!-- ★JNDI にする -->
<!-- ★data_source で JNDI からルックアップするための名を指定する -->
<property name="data_source" value="java:app/sampleDS"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="sample_mapper.xml"/>
</mappers>
</configuration>
-
<transactionManager>
タグのtype
をMANAGED
にする。 -
<datasource>
タグのtype
をJNDI
にする。 -
<property>
タグで、data_source
を設定する。- 値は JNDI ルックアップするための名前にする。
実行される SQL をログに出力する
logback の依存関係を追加すれば、 DEBUG レベルで出力される。
compile 'ch.qos.logback:logback-classic:1.1.3'
18:10:01.123 [main] DEBUG sample.mybatis.selectTest - ==> Preparing: select * from test_table where id=? and id=?
18:10:01.151 [main] DEBUG sample.mybatis.selectTest - ==> Parameters: 1(Integer), 1(Integer)
18:10:01.165 [main] DEBUG sample.mybatis.selectTest - <== Total: 1
実行された SQL と、バインドされたパラメータが出力されている。
実行した SQL のステートメント ID (sample.mybatis.selectTest
)がロガー名になっているので、ステートメント ID を利用したログ出力の制御ができるようになっている。
検索
複数件の結果を取得する
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<select id="selectTest" resultType="map">
select * from test_table
</select>
</mapper>
package sample.mybatis;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Main {
public static void main(String[] args) throws Exception {
try (InputStream in = Main.class.getResourceAsStream("/mybatis-config.xml")) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
try (SqlSession session = factory.openSession()) {
List<Map<String, Object>> result = session.selectList("sample.mybatis.selectTest");
System.out.println(result);
}
}
}
}
[{id=1, value=fizz}, {id=2, value=buzz}]
- 検索用の SQL は、
<select>
タグで宣言する。-
id
属性で、その SQL を参照するための識別名を設定する。 -
resultType
属性で、検索結果のレコードをどの型にマッピングするかを定義する。 - ここでは
map
と指定しているので、Map
型に変換される。
-
- 検索結果が複数のレコードを返す場合、
SqlSession
のselectList()
メソッドを使用する。- 戻り値は
List
型になる。
- 戻り値は
単一の結果を取得する
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<select id="selectTest" resultType="map">
select * from test_table limit 1
</select>
</mapper>
package sample.mybatis;
import java.io.InputStream;
import java.util.Map;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Main {
public static void main(String[] args) throws Exception {
try (InputStream in = Main.class.getResourceAsStream("/mybatis-config.xml")) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
try (SqlSession session = factory.openSession()) {
Map<String, Object> result = session.selectOne("sample.mybatis.selectTest");
System.out.println(result);
}
}
}
}
{id=1, value=fizz}
- 検索結果が1件だけで単一のオブジェクトで取得したい場合は、
selectOne()
メソッドを使用する。 - もし検索結果が複数のレコードを返した場合、
TooManyResultsException
がスローされる。 - 検索結果が 0 件の場合は
null
を返す。
resultType で指定できる値
resultType
には、検索結果を Java のどの型に変換するかを設定する。
通常は java.util.Map
のように変換先クラスの FQCN を指定する。
しかし、一部のよく利用する型については予めエイリアスが定義されており、短い名前で指定できる。
以下、 org.apache.ibatis.type.TypeAliasRegistry
より抽出。
エイリアス | 型 |
---|---|
string | String |
date | Date |
map | Map |
hashmap | HashMap |
list | List |
arraylist | ArrayList |
decimal | BigDecimal |
bigdecimal | BigDecimal |
biginteger | BigInteger |
_byte | byte |
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
_byte[] | byte[] |
_long[] | long[] |
_short[] | short[] |
_int[] | int[] |
_integer[] | int[] |
_double[] | double[] |
_float[] | float[] |
_boolean[] | boolean[] |
byte | Byte |
long | Long |
short | Short |
int | Integer |
integer | Integer |
double | Double |
float | Float |
boolean | Boolean |
byte[] | Byte[] |
long[] | Long[] |
short[] | Short[] |
int[] | Integer[] |
integer[] | Integer[] |
double[] | Double[] |
float[] | Float[] |
boolean[] | Boolean[] |
object | Object |
date[] | Date[] |
decimal[] | BigDecimal[] |
bigdecimal[] | BigDecimal[] |
biginteger[] | BigInteger[] |
object[] | Object[] |
collection | Collection |
iterator | Iterator |
ResultSet | ResultSet |
プリミティブ型の場合は _
を前に付ける。付けない場合はラッパークラスになるので注意。
エイリアスを定義する
エイリアスは任意に定義できる。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<typeAlias type="foo.bar.Hoge" alias="Hoge" />
</typeAliases>
...
</configuration>
- メインの方の設定ファイル(
mybatis-config.xml
)に<typeAlias>
タグで宣言できる。
パッケージを指定してエイリアスを定義する
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<package name="foo.bar"/>
</typeAliases>
...
</configuration>
-
<package>
タグを使うことで、パッケージ単位でエイリアスを定義できる。 - この場合、クラス名がそのままエイリアスになる。
パラメータ
単一の場合
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<select id="selectTest" resultType="map">
select * from test_table where id=#{id}
</select>
</mapper>
package sample.mybatis;
import java.io.InputStream;
import java.util.Map;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Main {
public static void main(String[] args) throws Exception {
try (InputStream in = Main.class.getResourceAsStream("/mybatis-config.xml")) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
try (SqlSession session = factory.openSession()) {
Map<String, Object> result = session.selectOne("sample.mybatis.selectTest", 1);
System.out.println(result);
}
}
}
}
[DEBUG] s.m.selectTest - ==> Preparing: select * from test_table where id=?
[DEBUG] s.m.selectTest - ==> Parameters: 1(Integer)
[DEBUG] s.m.selectTest - <== Total: 1
{id=1, value=fizz}
-
#{...}
という形式でパラメータを宣言できる。 - パラメータは、
SqlSession
で SQL を実行するときに、ステートメント ID (sample.mybatis.selectTest
)の次に引数で渡す。
複数の場合
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<!-- ★パラメータの型を指定する -->
<select id="selectTest" resultType="map" parameterType="sample.mybatis.MyParameter">
select *
from test_table
where id=#{id}
and value=#{value}
</select>
</mapper>
package sample.mybatis;
public class MyParameter {
private int id;
private String value;
public MyParameter(int id, String value) {
this.id = id;
this.value = value;
}
public int getId() {
System.out.println("get id");
return id;
}
}
package sample.mybatis;
import java.io.InputStream;
import java.util.Map;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Main {
public static void main(String[] args) throws Exception {
try (InputStream in = Main.class.getResourceAsStream("/mybatis-config.xml")) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
try (SqlSession session = factory.openSession()) {
MyParameter param = new MyParameter(2, "aaa");
Map<String, Object> result = session.selectOne("sample.mybatis.selectTest", param);
System.out.println(result);
}
}
}
}
get id
[DEBUG] s.m.selectTest - ==> Preparing: select * from test_table where id=? and value=?
get id
[DEBUG] s.m.selectTest - ==> Parameters: 2(Integer), aaa(String)
- 複数のパラメータを埋め込む場合、何かしらのクラスのインスタンスに値を入れて、それを渡すようにする。
- 入れ物となるクラスは、
<select>
タグのparameterType
属性で型を指定しておく。 - 宣言したパラメータの名前と一致するフィールド(プロパティ)の値がインスタンスから取得される。
- 値は、 Getter メソッドがあればそれ経由で、なければフィールドから直接取得される(
private
でも取得される模様)。
Map でパラメータを渡す
複数のパラメータを渡すには、 Map を使う方法も用意されている。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<select id="selectTest" resultType="map">
select *
from test_table
where id=#{id}
and value=#{value}
</select>
</mapper>
package sample.mybatis;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Main {
public static void main(String[] args) throws Exception {
try (InputStream in = Main.class.getResourceAsStream("/mybatis-config.xml")) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
try (SqlSession session = factory.openSession()) {
Map<String, Object> param = new HashMap<>();
param.put("id", 10);
param.put("value", "hogeeee");
Map<String, Object> result = session.selectOne("sample.mybatis.selectTest", param);
System.out.println(result);
}
}
}
}
[DEBUG] s.m.selectTest - ==> Preparing: select * from test_table where id=? and value=?
[DEBUG] s.m.selectTest - ==> Parameters: 10(Integer), hogeeee(String)
- パラメータは
Map
でも渡すことができる。 - その場合は、
<select>
タグのparameterType
の設定は無くても動く。
特殊なパラメータ名
以下のパラメータ名は、 MyBatis によって自動で登録される特殊なパラメータ名で、暗黙的に利用できる。
パラメータ名 | 条件 |
---|---|
_parameter |
(たぶん)常に参照可能 |
collection |
パラメータが java.util.Collection を実装している場合 |
list |
パラメータが java.util.List を実装している場合 |
array |
パラメータが配列の場合 |
-
_parameter
を設定している場所。org.apache.ibatis.scripting.xmltags.DynamicContext
-
collection
,list
,arrary
を設定している場所。org.apache.ibatis.session.defaults.DefaultSqlSession#wrapCollection(Object)
検索結果を任意の Java オブジェクトにマッピングする
基本
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<select id="selectTest" resultType="sample.mybatis.TestTable">
select * from test_table
</select>
</mapper>
package sample.mybatis;
public class TestTable {
private int id;
private String value;
public void setValue(String value) {
System.out.println("setValue(" + value + ")");
this.value = value;
}
@Override
public String toString() {
return "TestTable [id=" + id + ", value=" + value + "]";
}
}
package sample.mybatis;
import java.io.InputStream;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Main {
public static void main(String[] args) throws Exception {
try (InputStream in = Main.class.getResourceAsStream("/mybatis-config.xml")) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
try (SqlSession session = factory.openSession()) {
List<TestTable> result = session.selectList("sample.mybatis.selectTest");
System.out.println(result);
}
}
}
}
[DEBUG] s.m.selectTest - ==> Preparing: select * from test_table
[DEBUG] s.m.selectTest - ==> Parameters:
setValue(fizz)
setValue(buzz)
[DEBUG] s.m.selectTest - <== Total: 2
[TestTable [id=1, value=fizz], TestTable [id=2, value=buzz]]
-
resultType
に検索結果をマッピングしたい Java クラスを指定する。 - カラム名と一致するフィールドに値がセットされる。
- 値のセットは、 Setter メソッドがあればそれ経由で、なければフィールドに直接行われる。
スネークケースとキャメルケースのマッピングを自動化させる
test_table
package sample.mybatis;
public class TestTable {
private int id;
private String testValue;
@Override
public String toString() {
return "TestTable [id=" + id + ", testValue=" + testValue + "]";
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
...
</configuration>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<select id="selectTest" resultType="sample.mybatis.TestTable">
select * from test_table
</select>
</mapper>
TestTable [id=1, testValue=hoge]
TestTable [id=2, testValue=fuga]
TestTable [id=3, testValue=piyo]
-
mapUnderscoreToCamelCase
にtrue
を設定することで、スネークケースとキャメルケースの自動変換をしてくれるようになる。 - デフォルトは
false
になっている。
クラスごとにマッピングを定義する
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<resultMap id="testTableResultMap" type="sample.mybatis.TestTable">
<id property="id" column="id" />
<result property="value" column="value" />
</resultMap>
<select id="selectTest" resultMap="testTableResultMap">
select * from test_table
</select>
</mapper>
-
<resultMap>
タグを使って、検索結果と Java クラスとのマッピングを定義できる。 -
<id>
タグで、識別子プロパティを定義する。- 識別子プロパティは、インスタンスを識別するときに使用するプロパティを指す。
- 識別子プロパティを指定しておくことで、キャッシュや JOIN マッピングのときにパフォーマンスが向上するらしい。
-
property
属性で、 Java 側のプロパティ(フィールド)名を指定する。 -
column
属性で、データベース側の列名を指定する。
-
<result>
タグで、各列のマッピングを定義する。- こちらも
property
とcolumn
で名前をマッピングする。
- こちらも
-
<resultMap>
のid
属性でマッピングを一意に特定するための名前を定義する(testTableResultMap
)。- この名前を、
<select>
タグのresultMap
属性に指定する。
- この名前を、
単一のオブジェクトをマッピングする
基本
test_table
package sample.mybatis;
public class TestTable {
private int id;
private String string;
private Embedded embedded;
@Override
public String toString() {
return "TestTable [id=" + id + ", string=" + string + ", embedded=" + embedded + "]";
}
}
package sample.mybatis;
public class Embedded {
private int number;
@Override
public String toString() {
return "Embedded [number=" + number + "]";
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<resultMap type="sample.mybatis.TestTable" id="testTableResultMap">
<id property="id" column="id" />
<result property="string" column="string" />
<!-- ★association タグで定義 -->
<association property="embedded" javaType="sample.mybatis.Embedded">
<result property="number" column="number" />
</association>
</resultMap>
<select id="selectTest" resultMap="testTableResultMap">
select * from test_table
</select>
</mapper>
package sample.mybatis;
import java.io.InputStream;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Main {
public static void main(String[] args) throws Exception {
try (InputStream in = Main.class.getResourceAsStream("/mybatis-config.xml")) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
try (SqlSession session = factory.openSession()) {
session
.selectList("sample.mybatis.selectTest")
.forEach(System.out::println);
}
}
}
}
TestTable [id=1, string=hoge, embedded=Embedded [number=100]]
TestTable [id=2, string=fuga, embedded=Embedded [number=200]]
TestTable [id=3, string=piyo, embedded=Embedded [number=300]]
-
<association>
タグで、単一のクラスの埋め込みをマッピングできる。 -
javaType
属性で、埋め込むクラスを指定する(エイリアス可)。 - 子要素で、埋め込みクラスのマッピングを定義する。
埋め込むクラスの定義を外出しする
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<resultMap type="sample.mybatis.TestTable" id="testTableResultMap">
<id property="id" column="id" />
<result property="string" column="string" />
<!-- ★resutlMap を指定 -->
<association property="embedded" resultMap="embeddedResultMap" />
</resultMap>
<!-- ★別途 resultMap で定義 -->
<resultMap type="sample.mybatis.Embedded" id="embeddedResultMap">
<result property="number" column="number" />
</resultMap>
<select id="selectTest" resultMap="testTableResultMap">
select * from test_table
</select>
</mapper>
-
<resultMap>
で別途定義できる。
カラム名にプレフィックスを指定する
foo_table
bar_table
package sample.mybatis;
public class Foo {
private int id;
private Bar bar;
@Override
public String toString() {
return "Foo [id=" + id + ", bar=" + bar + "]";
}
}
package sample.mybatis;
public class Bar {
private int id;
@Override
public String toString() {
return "Bar [id=" + id + "]";
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<resultMap type="sample.mybatis.Foo" id="fooResultMap">
<id property="id" column="id" />
<!-- ★columnPrefix で共通するプレフィックスを指定する -->
<association property="bar" columnPrefix="bar_" resultMap="barResultMap" />
</resultMap>
<resultMap type="sample.mybatis.Bar" id="barResultMap">
<id property="id" column="id" />
</resultMap>
<select id="selectFoo" resultMap="fooResultMap">
select foo.id
,bar.id bar_id -- ★"bar_" をプレフィックスとして付ける
from foo_table foo
,bar_table bar
where bar.id = foo.bar_id
order by foo.id asc
</select>
</mapper>
Foo [id=1, bar=Bar [id=3]]
Foo [id=2, bar=Bar [id=2]]
Foo [id=3, bar=Bar [id=1]]
- JOIN とかしたときに別テーブルの同名カラムと区別するために、よくプレフィックス(
bar_
)を付けると思う。 - そういうときでも
resultMap
を使い回すことができるよう、columnPrefix
という属性が用意されている。 -
columnPrefix
を指定すれば、column
の設定値にプレフィックスを付けた名称でマッピングが行われるようになる。
SELECT を分けて実行する
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<resultMap type="sample.mybatis.Foo" id="fooResultMap">
<id property="id" column="id" />
<association property="bar" column="bar_id" select="selectBar" />
</resultMap>
<select id="selectFoo" resultMap="fooResultMap">
select * from foo_table
</select>
<select id="selectBar" resultType="sample.mybatis.Bar">
select * from bar_table where id = #{id}
</select>
</mapper>
[DEBUG] s.m.selectFoo - ==> Preparing: select * from foo_table
[DEBUG] s.m.selectFoo - ==> Parameters:
[DEBUG] s.m.selectBar - ====> Preparing: select * from bar_table where id = ?
[DEBUG] s.m.selectBar - ====> Parameters: 1(Integer)
[DEBUG] s.m.selectBar - <==== Total: 1
[DEBUG] s.m.selectBar - ====> Preparing: select * from bar_table where id = ?
[DEBUG] s.m.selectBar - ====> Parameters: 2(Integer)
[DEBUG] s.m.selectBar - <==== Total: 1
[DEBUG] s.m.selectBar - ====> Preparing: select * from bar_table where id = ?
[DEBUG] s.m.selectBar - ====> Parameters: 3(Integer)
[DEBUG] s.m.selectBar - <==== Total: 1
[DEBUG] s.m.selectFoo - <== Total: 3
Foo [id=3, bar=Bar [id=1]]
Foo [id=2, bar=Bar [id=2]]
Foo [id=1, bar=Bar [id=3]]
-
foo_table
を検索したあと、各列に対してbar_table
の検索を別途実行している。 -
<association>
タグのselect
属性で、<select>
タグのステートメントID を指定することで、こういう検索にできる。 -
column
属性には、子テーブルの JOIN に使用するカラムを指定する。
JOIN キーが複数ある場合
foo_table
bar_table
package sample.mybatis;
public class Bar {
private int key1;
private String key2;
@Override
public String toString() {
return "Bar [key1=" + key1 + ", key2=" + key2 + "]";
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<resultMap type="sample.mybatis.Foo" id="fooResultMap">
<id property="id" column="id" />
<!-- ★キーが複数ある場合の指定方法 -->
<association property="bar" column="{key1=bar_key1,key2=bar_key2}" select="selectBar" />
</resultMap>
<select id="selectFoo" resultMap="fooResultMap">
select * from foo_table
</select>
<select id="selectBar" resultType="sample.mybatis.Bar">
select *
from bar_table
where key1 = #{key1}
and key2 = #{key2}
</select>
</mapper>
[DEBUG] s.m.selectFoo - ==> Preparing: select * from foo_table
[DEBUG] s.m.selectFoo - ==> Parameters:
[DEBUG] s.m.selectBar - ====> Preparing: select * from bar_table where key1 = ? and key2 = ?
[DEBUG] s.m.selectBar - ====> Parameters: 1(Integer), fuga(String)
[DEBUG] s.m.selectBar - <==== Total: 1
[DEBUG] s.m.selectBar - ====> Preparing: select * from bar_table where key1 = ? and key2 = ?
[DEBUG] s.m.selectBar - ====> Parameters: 1(Integer), hoge(String)
[DEBUG] s.m.selectBar - <==== Total: 1
[DEBUG] s.m.selectBar - ====> Preparing: select * from bar_table where key1 = ? and key2 = ?
[DEBUG] s.m.selectBar - ====> Parameters: 2(Integer), hoge(String)
[DEBUG] s.m.selectBar - <==== Total: 1
[DEBUG] s.m.selectFoo - <== Total: 3
Foo [id=2, bar=Bar [key1=1, key2=fuga]]
Foo [id=3, bar=Bar [key1=1, key2=hoge]]
Foo [id=1, bar=Bar [key1=2, key2=hoge]]
- JOIN するときのカラムが複数存在する場合は、
{prop1=column1,prop2=column2}
という形でcolumn
属性を指定する。
関連するオブジェクトの読み込みを遅延させる
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<resultMap type="sample.mybatis.Foo" id="fooResultMap">
<id property="id" column="id" />
<association property="bar" column="{key1=bar_key1,key2=bar_key2}" select="selectBar"
fetchType="lazy" /> <!-- ★fetchType に lazy を指定 -->
</resultMap>
<select id="selectFoo" resultMap="fooResultMap">
select * from foo_table
</select>
<select id="selectBar" resultType="sample.mybatis.Bar">
select *
from bar_table
where key1 = #{key1}
and key2 = #{key2}
</select>
</mapper>
package sample.mybatis;
public class Foo {
private int id;
public Bar bar;
public void method() {
System.out.println("Foo.method() is called");
}
}
package sample.mybatis;
import java.io.InputStream;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Main {
public static void main(String[] args) throws Exception {
try (InputStream in = Main.class.getResourceAsStream("/mybatis-config.xml")) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
try (SqlSession session = factory.openSession()) {
System.out.println("@selectList()");
List<Foo> result = session.selectList("sample.mybatis.selectFoo");
System.out.println("@result.get(0)");
Foo foo = result.get(0);
System.out.println("** foo.class = " + foo.getClass() + " **");
System.out.println("@foo.bar 1");
System.out.println("<< foo.bar = " + foo.bar + " >>");
System.out.println("@foo.method()");
foo.method();
System.out.println("@foo.bar 2");
System.out.println("<< foo.bar = " + foo.bar + " >>");
}
}
}
}
@selectList()
[DEBUG] s.m.selectFoo - ==> Preparing: select * from foo_table
[DEBUG] s.m.selectFoo - ==> Parameters:
[DEBUG] s.m.selectFoo - <== Total: 3
@result.get(0)
** foo.class = class sample.mybatis.Foo_$$_jvst80f_0 **
@foo.bar 1
<< foo.bar = null >>
@foo.method()
[DEBUG] s.m.selectBar - ==> Preparing: select * from bar_table where key1 = ? and key2 = ?
[DEBUG] s.m.selectBar - ==> Parameters: 1(Integer), fuga(String)
[DEBUG] s.m.selectBar - <== Total: 1
Foo.method() is called
@foo.bar 2
<< foo.bar = Bar [key1=1, key2=fuga] >>
-
<association>
のfetchType
属性にlazy
とセットすると、関連するオブジェクトの読み込みを遅延させることができる。- デフォルトは即時ロード(
eager
)。
- デフォルトは即時ロード(
- 遅延が設定されたクラスのインスタンスは、 MyBatis によって生成されたプロキシになる(
class sample.mybatis.Foo_$$_jvst80f_0
)。- 遅延ロードでない場合は、普通のインスタンスが渡される。
- 遅延ロードが実行されるタイミングは、「プロキシのいずれかのメソッドが実行されたときの先頭」の模様。
- フィールドが読み取られたときではないようなので、メソッド実行前に参照すると
null
になっている(まぁ、直接参照することはないと思うけど、一応注意)。
全ての関連を遅延ロードさせたい場合は、ルートの設定で lazyLoadingEnabled
を設定する方法がある。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="lazyLoadingEnabled" value="true"/> <!-- ★true にする -->
</settings>
...
</configuration>
この場合でも、 <association>
タグの fetchType
の方が優先されるので、特定の読み込みだけ即時ロードにしたい場合は、設定を上書きできる。
コレクションをマッピングする
基本
foo_table
bar_table
package sample.mybatis;
import java.util.List;
public class Foo {
private int id;
private List<Bar> barList;
@Override
public String toString() {
return "Foo [id=" + id + ", barList=" + barList + "]";
}
}
package sample.mybatis;
public class Bar {
private int id;
@Override
public String toString() {
return "Bar [id=" + id + "]";
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<resultMap type="sample.mybatis.Foo" id="fooResultMap">
<id property="id" column="id" />
<!-- ★collection タグを使用する -->
<collection property="barList" ofType="sample.mybatis.Bar">
<id property="id" column="bar_id" />
</collection>
</resultMap>
<select id="selectFoo" resultMap="fooResultMap">
select foo.id
,bar.id bar_id
from foo_table foo
,bar_table bar
where bar.foo_id = foo.id
</select>
</mapper>
[DEBUG] s.m.selectFoo - ==> Preparing: select foo.id ,bar.id bar_id from foo_table foo ,bar_table bar where bar.foo_id = foo.id
[DEBUG] s.m.selectFoo - ==> Parameters:
[DEBUG] s.m.selectFoo - <== Total: 6
Foo [id=1, barList=[Bar [id=1], Bar [id=2], Bar [id=3]]]
Foo [id=2, barList=[Bar [id=4], Bar [id=5]]]
Foo [id=3, barList=[Bar [id=6]]]
- コレクションをマッピングする場合は、
<collection>
タグを使用する。 - 各要素の型は
ofType
属性で指定する(エイリアス可)。 - それ以外は、
<association>
タグのときと同じノリでいける。
SELECT を分割して実行する
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<resultMap type="sample.mybatis.Foo" id="fooResultMap">
<id property="id" column="id" />
<collection property="barList" column="id" select="selectBar" />
</resultMap>
<select id="selectFoo" resultMap="fooResultMap">
select * from foo_table
</select>
<select id="selectBar" resultType="sample.mybatis.Bar">
select * from bar_table where foo_id = #{id}
</select>
</mapper>
[DEBUG] s.m.selectFoo - ==> Preparing: select * from foo_table
[DEBUG] s.m.selectFoo - ==> Parameters:
[DEBUG] s.m.selectBar - ====> Preparing: select * from bar_table where foo_id = ?
[DEBUG] s.m.selectBar - ====> Parameters: 1(Integer)
[DEBUG] s.m.selectBar - <==== Total: 3
[DEBUG] s.m.selectBar - ====> Preparing: select * from bar_table where foo_id = ?
[DEBUG] s.m.selectBar - ====> Parameters: 2(Integer)
[DEBUG] s.m.selectBar - <==== Total: 2
[DEBUG] s.m.selectBar - ====> Preparing: select * from bar_table where foo_id = ?
[DEBUG] s.m.selectBar - ====> Parameters: 3(Integer)
[DEBUG] s.m.selectBar - <==== Total: 1
[DEBUG] s.m.selectFoo - <== Total: 3
Foo [id=1, barList=[Bar [id=1], Bar [id=2], Bar [id=3]]]
Foo [id=2, barList=[Bar [id=4], Bar [id=5]]]
Foo [id=3, barList=[Bar [id=6]]]
-
<association>
のときと同じノリでできる。 - 遅延ロードも同じく可能。
条件によって異なるクラスにマッピングする
主に継承関係にあるクラスをマッピングするときに使用する方法。
base_table
package sample.mybatis;
public class Base {
protected int id;
protected String commonValue;
@Override
public String toString() {
return "Base [id=" + id + ", commonValue=" + commonValue + "]";
}
}
package sample.mybatis;
public class Hoge extends Base {
private String value;
@Override
public String toString() {
return "Hoge [value=" + value + ", id=" + id + ", commonValue=" + commonValue + "]";
}
}
package sample.mybatis;
public class Fuga extends Base {
private String value;
@Override
public String toString() {
return "Fuga [value=" + value + ", id=" + id + ", commonValue=" + commonValue + "]";
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<resultMap type="sample.mybatis.Base" id="baseResultMap">
<id property="id" column="id" />
<result property="commonValue" column="common_value" />
<discriminator javaType="int" column="type">
<case value="1" resultType="sample.mybatis.Hoge">
<result property="value" column="hoge_value" />
</case>
<case value="2" resultType="sample.mybatis.Fuga">
<result property="value" column="fuga_value" />
</case>
</discriminator>
</resultMap>
<select id="selectBase" resultMap="baseResultMap">
select * from base_table
</select>
</mapper>
Hoge [value=hoge1, id=1, commonValue=common1]
Fuga [value=fuga2, id=2, commonValue=common2]
Base [id=3, commonValue=common3]
-
<discriminator>
タグを使うことで、条件によって異なるクラスにマッピングさせることができるようになる。-
column
属性で、条件値を持つカラムを指定する。
-
-
<case>
タグで、条件値ごとにどういうマッピングを行うかを定義する。- ここでは、
1
の場合はHoge
クラスに、2
の場合はFuga
クラスにマッピングするように定義している。
- ここでは、
-
<case>
式に存在しない条件値の場合は、もともとの<resultMap>
タグで指定していたtype
にマッピングされる。
ケースごとのマッピングを外出しする
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<resultMap type="sample.mybatis.Base" id="baseResultMap">
<id property="id" column="id" />
<result property="commonValue" column="common_value" />
<discriminator javaType="int" column="type">
<case value="1" resultMap="hogeResultMap" />
<case value="2" resultMap="fugaResultMap" />
</discriminator>
</resultMap>
<resultMap type="sample.mybatis.Hoge" id="hogeResultMap">
<result property="value" column="hoge_value" />
</resultMap>
<resultMap type="sample.mybatis.Fuga" id="fugaResultMap">
<result property="value" column="fuga_value" />
<result property="commonValue" column="common_value" />
</resultMap>
<select id="selectBase" resultMap="baseResultMap">
select * from base_table
</select>
</mapper>
Hoge [value=hoge1, id=1, commonValue=null]
Fuga [value=fuga2, id=2, commonValue=common2]
Base [id=3, commonValue=common3]
-
<case>
ごとの定義は、<resultMap>
で抽出できる。 - ただし、その場合共通部分で定義していたカラムは
<id>
以外はマッピングされないので、個々の<resultMap>
で定義しないといけない。-
hogeResultMap
は、commonValue
を定義していないのでnull
になっている。
-
- いちいち個々の
<resultMap>
で共通項目を定義しなおすのがシンドイ場合は、extends
属性を使う方法が用意されている。
extends 属性で共通部分を継承する
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<resultMap type="sample.mybatis.Base" id="baseResultMap">
<id property="id" column="id" />
<result property="commonValue" column="common_value" />
<discriminator javaType="int" column="type">
<case value="1" resultMap="hogeResultMap" />
<case value="2" resultMap="fugaResultMap" />
</discriminator>
</resultMap>
<!-- ★extends で baseResultMap を継承 -->
<resultMap type="sample.mybatis.Hoge" id="hogeResultMap" extends="baseResultMap">
<result property="value" column="hoge_value" />
</resultMap>
<resultMap type="sample.mybatis.Fuga" id="fugaResultMap" extends="baseResultMap">
<result property="value" column="fuga_value" />
</resultMap>
<select id="selectBase" resultMap="baseResultMap">
select * from base_table
</select>
</mapper>
Hoge [value=hoge1, id=1, commonValue=common1]
Fuga [value=fuga2, id=2, commonValue=common2]
Base [id=3, commonValue=common3]
キャッシュ
package sample.mybatis;
import java.io.InputStream;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Main {
public static void main(String[] args) throws Exception {
try (InputStream in = Main.class.getResourceAsStream("/mybatis-config.xml")) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
try (SqlSession session1 = factory.openSession();
SqlSession session2 = factory.openSession();) {
TestTable testTable = selectAndPrintln("session1", session1);
testTable.setValue("update");
selectAndPrintln("session1", session1);
selectAndPrintln("session2", session2);
}
}
}
private static TestTable selectAndPrintln(String tag, SqlSession session) {
TestTable result = session.selectOne("sample.mybatis.selectTest");
System.out.printf("<<%s>> %s%n", tag, result);
return result;
}
}
[DEBUG] s.m.selectTest - ==> Preparing: select * from test_table where id=1
[DEBUG] s.m.selectTest - ==> Parameters:
[DEBUG] s.m.selectTest - <== Total: 1
<<session1>> TestTable [id=1, value=hoge]
<<session1>> TestTable [id=1, value=update]
[DEBUG] s.m.selectTest - ==> Preparing: select * from test_table where id=1
[DEBUG] s.m.selectTest - ==> Parameters:
[DEBUG] s.m.selectTest - <== Total: 1
<<session2>> TestTable [id=1, value=hoge]
- 検索結果はキャッシュされる。
- 同じ検索を再度実行すると、キャッシュされているオブジェクトが返される。
- キャッシュはセッションごとに保持されるため、他のセッションでの変更の影響は受けない。
- デフォルトは、以下のような動作をするらしい。
- 1024 個のオブジェクトをキャッシュする。
- 最も使われていないキャッシュから削除されていく。
- 時間経過では削除されない。
-
insert
,update
,delete
が実行されるとキャッシュはクリアされる。
- このデフォルトの動作は設定ファイルで上書きすることができる。
- 設定値の詳細は こちら を参照。
更新系処理
登録
基本
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<insert id="insertTest">
insert into test_table (
value
) values (
#{value}
)
</insert>
</mapper>
package sample.mybatis;
import java.io.InputStream;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Main {
public static void main(String[] args) throws Exception {
try (InputStream in = Main.class.getResourceAsStream("/mybatis-config.xml")) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
try (SqlSession session = factory.openSession()) {
session.insert("sample.mybatis.insertTest", "hoge");
session.commit();
}
}
}
}
[DEBUG] s.m.insertTest - ==> Preparing: insert into test_table ( value ) values ( ? )
[DEBUG] s.m.insertTest - ==> Parameters: hoge(String)
[DEBUG] s.m.insertTest - <== Updates: 1
test_table
-
<insert>
タグでINSERT
文を定義できる。 - だいたい
<select>
のときと同じノリで定義できる。
オブジェクトをパラメータとして渡す
package sample.mybatis;
public class TestTable {
private int id;
private String value;
public TestTable(String value) {
this.value = value;
}
@Override
public String toString() {
return "TestTable [id=" + id + ", value=" + value + "]";
}
}
package sample.mybatis;
import java.io.InputStream;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Main {
public static void main(String[] args) throws Exception {
try (InputStream in = Main.class.getResourceAsStream("/mybatis-config.xml")) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
try (SqlSession session = factory.openSession()) {
TestTable table = new TestTable("fuga");
session.insert("sample.mybatis.insertTest", table);
session.commit();
}
}
}
}
[DEBUG] s.m.insertTest - ==> Preparing: insert into test_table ( value ) values ( ? )
[DEBUG] s.m.insertTest - ==> Parameters: fuga(String)
[DEBUG] s.m.insertTest - <== Updates: 1
- こちらも
<select>
のときと同じ感じ。 -
parameterType
は省略可能。
自動生成されたキーを取得する
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<insert id="insertTest" useGeneratedKeys="true" keyProperty="id">
insert into test_table (
value
) values (
#{value}
)
</insert>
</mapper>
package sample.mybatis;
import java.io.InputStream;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Main {
public static void main(String[] args) throws Exception {
try (InputStream in = Main.class.getResourceAsStream("/mybatis-config.xml")) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
try (SqlSession session = factory.openSession()) {
TestTable table = new TestTable("piyo");
session.insert("sample.mybatis.insertTest", table);
session.commit();
System.out.println(table);
}
}
}
}
[DEBUG] s.m.insertTest - ==> Preparing: insert into test_table ( value ) values ( ? )
[DEBUG] s.m.insertTest - ==> Parameters: fuga(String)
[DEBUG] s.m.insertTest - <== Updates: 1
TestTable [id=7, value=piyo]
-
<insert>
タグにuseGeneratedKeys
とkeyProperty
を指定する。-
useGeneratedKeys
はtrue
を指定。 -
keyProperty
にはキー値を設定する Java 側のフィールド(プロパティ)名を指定する。
-
- すると、
INSERT
時にデータベースによって生成された ID が自動で Java のインスタンスにセットされるようになる。
Oracle のシーケンスオブジェクトの場合
ID をシーケンスオブジェクトから取得する場合は、以下のようにする。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<insert id="insertTest">
<selectKey keyProperty="id" resultType="_int" order="BEFORE">
select test_table_seq.nextval
from dual
</selectKey>
insert into test_table (
id
,value
) values (
#{id}
,#{value}
)
</insert>
</mapper>
[DEBUG] s.m.insertTest!selectKey - ==> Preparing: select test_table_seq.nextval from dual
[DEBUG] s.m.insertTest!selectKey - ==> Parameters:
[DEBUG] s.m.insertTest!selectKey - <== Total: 1
[DEBUG] s.m.insertTest - ==> Preparing: insert into test_table ( id ,value ) values ( ? ,? )
[DEBUG] s.m.insertTest - ==> Parameters: 11(Integer), aaa(String)
[DEBUG] s.m.insertTest - <== Updates: 1
TestTable [id=11, value=aaa]
SQL> select * from test_table;
ID VALUE
---------- -----
11 aaa
-
<insert>
タグの中に<selectKey>
タグを宣言する。 -
keyProperty
属性で、生成された ID をセットする Java フィールド(プロパティ)名を指定する。 -
order
属性にBEFORE
をセットすることで、まずシーケンスから値を取得してからINSERT
が実行されるようになる。
更新
test_table
package sample.mybatis;
public class TestTable {
private int id;
private String value;
public TestTable(int id, String value) {
this.id = id;
this.value = value;
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<update id="updateTest">
update test_table
set value = #{value}
where id = #{id}
</update>
</mapper>
package sample.mybatis;
import java.io.InputStream;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Main {
public static void main(String[] args) throws Exception {
try (InputStream in = Main.class.getResourceAsStream("/mybatis-config.xml")) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
try (SqlSession session = factory.openSession()) {
TestTable table = new TestTable(1, "update");
session.insert("sample.mybatis.updateTest", table);
session.commit();
}
}
}
}
[DEBUG] s.m.updateTest - ==> Preparing: update test_table set value = ? where id = ?
[DEBUG] s.m.updateTest - ==> Parameters: update(String), 1(Integer)
[DEBUG] s.m.updateTest - <== Updates: 1
-
<update>
タグでUPDATE
を定義できる。
削除
test_table
package sample.mybatis;
public class TestTable {
private int id;
private String value;
public TestTable(int id) {
this.id = id;
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<delete id="deleteTest">
delete from test_table where id = #{id}
</delete>
</mapper>
package sample.mybatis;
import java.io.InputStream;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Main {
public static void main(String[] args) throws Exception {
try (InputStream in = Main.class.getResourceAsStream("/mybatis-config.xml")) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
try (SqlSession session = factory.openSession()) {
TestTable table = new TestTable(2);
session.insert("sample.mybatis.deleteTest", table);
session.commit();
}
}
}
}
[DEBUG] s.m.deleteTest - ==> Preparing: delete from test_table where id = ?
[DEBUG] s.m.deleteTest - ==> Parameters: 2(Integer)
[DEBUG] s.m.deleteTest - <== Updates: 1
-
<delete>
タグでDELETE
文を定義できる。
バッチ更新
デフォルトでは、 SQL を実行するたびに PreparedStatement
を生成して SQL をデータベースに対して発行している。
更新件数が多くなる場合、これは非常に効率が悪い。
MyBatis では、設定により PreparedStatement
の使い回しや、バッチ更新ができるようになっている。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<insert id="insertTest">
insert into test_table (value) values (#{value})
</insert>
</mapper>
package sample.mybatis;
import java.io.InputStream;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Main {
public static void main(String[] args) throws Exception {
try (InputStream in = Main.class.getResourceAsStream("/mybatis-config.xml")) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
try (SqlSession session = factory.openSession()) {
for (int i=0; i<3; i++) {
TestTable t = new TestTable("value-" + i);
int updateCount = session.insert("sample.mybatis.insertTest", t);
System.out.println("updateCount = " + updateCount);
}
session.commit();
}
}
}
}
- 3回ループして、
INSERT
を実行する実装。
デフォルトの動作
[DEBUG] s.m.insertTest - ==> Preparing: insert into test_table (value) values (?)
[DEBUG] s.m.insertTest - ==> Parameters: value-0(String)
[DEBUG] s.m.insertTest - <== Updates: 1
updateCount = 1
[DEBUG] s.m.insertTest - ==> Preparing: insert into test_table (value) values (?)
[DEBUG] s.m.insertTest - ==> Parameters: value-1(String)
[DEBUG] s.m.insertTest - <== Updates: 1
updateCount = 1
[DEBUG] s.m.insertTest - ==> Preparing: insert into test_table (value) values (?)
[DEBUG] s.m.insertTest - ==> Parameters: value-2(String)
[DEBUG] s.m.insertTest - <== Updates: 1
updateCount = 1
- 毎回
PreparedStatement
が生成されて、 SQL が発行されている。
PreparedStatement を再利用する
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="defaultExecutorType" value="REUSE"/>
</settings>
...
</configuration>
[DEBUG] s.m.insertTest - ==> Preparing: insert into test_table (value) values (?)
[DEBUG] s.m.insertTest - ==> Parameters: value-0(String)
[DEBUG] s.m.insertTest - <== Updates: 1
updateCount = 1
[DEBUG] s.m.insertTest - ==> Parameters: value-1(String)
[DEBUG] s.m.insertTest - <== Updates: 1
updateCount = 1
[DEBUG] s.m.insertTest - ==> Parameters: value-2(String)
[DEBUG] s.m.insertTest - <== Updates: 1
updateCount = 1
- 設定ファイルで
defaultExecutorType
にREUSE
を設定する。 -
PreparedStatement
の生成は1回だけになる。 - SQL の発行は毎回行われている。
バッチ更新にする
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="defaultExecutorType" value="BATCH"/>
</settings>
...
</configuration>
[DEBUG] s.m.insertTest - ==> Preparing: insert into test_table (value) values (?)
[DEBUG] s.m.insertTest - ==> Parameters: value-0(String)
updateCount = -2147482646
[DEBUG] s.m.insertTest - ==> Parameters: value-1(String)
updateCount = -2147482646
[DEBUG] s.m.insertTest - ==> Parameters: value-2(String)
updateCount = -2147482646
-
defaultExecutorType
をBATCH
にするとバッチ更新になる。 - SQL の発行は最後にまとめて行われる。
- なので、更新件数は取得できなくなっている。
SqlSession を取得するときに指定する
バッチ更新にするかどうかの設定は、 SqlSession
を取得するときの引数でも指定できる。
import org.apache.ibatis.session.ExecutorType;
...
SqlSession session = factory.openSession(ExecutorType.BATCH);
-
openSession()
の引数にExecutorType
を渡すことで指定できる。 - ここで指定した条件は、設定ファイルよりも優先される。
Mapper
ここまでは、 SqlSession
に直接ステートメント ID とパラメータを渡す方法で SQL を実行していた。
MyBatis には、これとは別に Mapper という仕組みも用意されている。
Mapper を使うと、 SqlSession
を直接使うより型安全になる。
基本的にこの Mapper を使用した方法の方が推奨っぽい。
(マッピングのファイルが自然と Mapper 単位になるので、管理もしやすくなるっぽい)
基本
test_table
package sample.mybatis;
public class TestTable {
private int id;
private String string;
private int number;
@Override
public String toString() {
return "TestTable [id=" + id + ", string=" + string + ", number=" + number + "]";
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis.TestTableMapper"> <!-- ★Mapper の FQCN を namespace にする -->
<select id="selectAll" resultType="sample.mybatis.TestTable">
select *
from test_table
</select>
</mapper>
package sample.mybatis;
import java.util.List;
// ★ Mapper インターフェース
public interface TestTableMapper {
// ★ステートメントID と同じ名前のメソッドを定義する
List<TestTable> selectAll();
}
package sample.mybatis;
import java.io.InputStream;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Main {
public static void main(String[] args) throws Exception {
try (InputStream in = Main.class.getResourceAsStream("/mybatis-config.xml")) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
try (SqlSession session = factory.openSession()) {
// ★Mapper のインスタンスを取得する
TestTableMapper mapper = session.getMapper(TestTableMapper.class);
mapper.selectAll().forEach(System.out::println);
}
}
}
}
[DEBUG] s.m.T.selectAll - ==> Preparing: select * from test_table
[DEBUG] s.m.T.selectAll - ==> Parameters:
[DEBUG] s.m.T.selectAll - <== Total: 3
TestTable [id=1, string=hoge, number=100]
TestTable [id=2, string=fuga, number=200]
TestTable [id=3, string=piyo, number=300]
- Mapper を利用する場合、専用のインターフェースを定義する(
TestTableMapper
)。- このインターフェースのメソッドと、設定ファイルの各ステートメントが対応するようにそれぞれを定義する。
- ステートメントの方は、
namespace
をインターフェースの FQCN にする(sample.mybatis.TestTableMapper
)。 - ステートメントID とインターフェースのメソッド名を一致させる(
selectAll
)。 - パラメータが存在する場合は、メソッドに引数を渡すように定義する。
- Mapper のインスタンスは、
SqlSession
のgetMapper()
メソッドで取得する。
パラメータを複数の引数に分けて渡す
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis.TestTableMapper">
<select id="selectByStringOrNumber" resultType="sample.mybatis.TestTable">
select *
from test_table
where string = #{string}
or number = #{number}
</select>
</mapper>
package sample.mybatis;
import java.util.List;
import org.apache.ibatis.annotations.Param;
public interface TestTableMapper {
List<TestTable> selectByStringOrNumber(
@Param("string") String string,
@Param("number") int number
);
}
package sample.mybatis;
import java.io.InputStream;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Main {
public static void main(String[] args) throws Exception {
try (InputStream in = Main.class.getResourceAsStream("/mybatis-config.xml")) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
try (SqlSession session = factory.openSession()) {
TestTableMapper mapper = session.getMapper(TestTableMapper.class);
mapper.selectByStringOrNumber("hoge", 300).forEach(System.out::println);
}
}
}
}
[DEBUG] s.m.T.selectByStringOrNumber - ==> Preparing: select * from test_table where string = ? or number = ?
[DEBUG] s.m.T.selectByStringOrNumber - ==> Parameters: hoge(String), 300(Integer)
[DEBUG] s.m.T.selectByStringOrNumber - <== Total: 2
TestTable [id=1, string=hoge, number=100]
TestTable [id=3, string=piyo, number=300]
- パラメータが複数存在するケースで、メソッドの引数をそれぞれ分けて定義したい場合は
@Param
アノテーションで引数をアノテートする。 -
@Param
のvalue
には、パラメータの名前を指定する。
動的 SQL
条件に合わせて SQL を動的に組み立てることができる。
if
test_table
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<select id="selectTest" resultType="sample.mybatis.TestTable">
select *
from test_table
where string_value = 'hoge'
<if test="numberValue != null"> <!-- ★if タグで条件分岐 -->
and number_value = #{numberValue}
</if>
</select>
</mapper>
package sample.mybatis;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Main {
public static void main(String[] args) throws Exception {
try (InputStream in = Main.class.getResourceAsStream("/mybatis-config.xml")) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
try (SqlSession session = factory.openSession()) {
session
.selectList("sample.mybatis.selectTest") // ★パラメータ未設定
.forEach(System.out::println);
Map<String, Object> param = new HashMap<>();
param.put("numberValue", 100);
session
.selectList("sample.mybatis.selectTest", param) // ★パラメータを設定
.forEach(System.out::println);
}
}
}
}
[DEBUG] s.m.selectTest - ==> Preparing: select * from test_table where string_value = 'hoge'
[DEBUG] s.m.selectTest - ==> Parameters:
[DEBUG] s.m.selectTest - <== Total: 2
TestTable [id=1, stringValue=hoge, numberValue=100]
TestTable [id=2, stringValue=hoge, numberValue=200]
[DEBUG] s.m.selectTest - ==> Preparing: select * from test_table where string_value = 'hoge' and number_value = ?
[DEBUG] s.m.selectTest - ==> Parameters: 100(Integer)
[DEBUG] s.m.selectTest - <== Total: 1
TestTable [id=1, stringValue=hoge, numberValue=100]
-
<if>
タグを使用することで、条件が満たされたときだけ SQL を追加させたりできるようになる。 -
test
属性で条件式を記述する。- この中では、検索条件で渡すパラメータの値を参照できる。
- AND や OR 条件を記述するときは、
and
,or
を使用する(&&
,||
ではない!)。
choose
test_table
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<select id="selectTest" resultType="sample.mybatis.TestTable">
select *
from test_table
<choose>
<when test="value == null">
where value is null
</when>
<otherwise>
where value = #{value}
</otherwise>
</choose>
</select>
</mapper>
package sample.mybatis;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Main {
public static void main(String[] args) throws Exception {
try (InputStream in = Main.class.getResourceAsStream("/mybatis-config.xml")) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
try (SqlSession session = factory.openSession()) {
TestTable testTable = session.selectOne("sample.mybatis.selectTest");
System.out.println(testTable);
Map<String, Object> param = new HashMap<>();
param.put("value", "hoge");
testTable = session.selectOne("sample.mybatis.selectTest", param);
System.out.println(testTable);
}
}
}
}
[DEBUG] s.m.selectTest - ==> Preparing: select * from test_table where value is null
[DEBUG] s.m.selectTest - ==> Parameters:
[DEBUG] s.m.selectTest - <== Total: 1
TestTable [id=2, value=null]
[DEBUG] s.m.selectTest - ==> Preparing: select * from test_table where value = ?
[DEBUG] s.m.selectTest - ==> Parameters: hoge(String)
[DEBUG] s.m.selectTest - <== Total: 1
TestTable [id=1, value=hoge]
-
<choose>
タグを使うことで、複数の選択肢のうち1つだけを適用するという条件を定義できる。 -
<when>
タグで、指定した条件が満たされた場合の SQL を記述する。 -
<otherwise>
タグで、それ以外の条件が全て満たされなかった場合の SQL を記述する。
where
test_table
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<select id="selectTest" resultType="sample.mybatis.TestTable">
select *
from test_table
<where>
<if test="string != null">
and string = #{string}
</if>
<if test="number != null">
and number = #{number}
</if>
</where>
</select>
</mapper>
package sample.mybatis;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Main {
public static void main(String[] args) throws Exception {
try (InputStream in = Main.class.getResourceAsStream("/mybatis-config.xml")) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
try (SqlSession session = factory.openSession()) {
// ★パラメータを空で実行
Map<String, Object> param = new HashMap<>();
selectAndPrint(session, param);
// ★パラメータに string だけ指定して実行
param = new HashMap<>();
param.put("string", "hoge");
selectAndPrint(session, param);
// ★パラメータに string と number を指定して実行
param = new HashMap<>();
param.put("string", "hoge");
param.put("number", 200);
selectAndPrint(session, param);
}
}
}
private static void selectAndPrint(SqlSession session, Map<String, Object> param) {
session
.selectList("sample.mybatis.selectTest", param)
.forEach(System.out::println);
System.out.println();
}
}
[DEBUG] s.m.selectTest - ==> Preparing: select * from test_table
[DEBUG] s.m.selectTest - ==> Parameters:
[DEBUG] s.m.selectTest - <== Total: 4
TestTable [id=1, string=hoge, number=100]
TestTable [id=2, string=hoge, number=200]
TestTable [id=3, string=fuga, number=200]
TestTable [id=4, string=piyo, number=300]
[DEBUG] s.m.selectTest - ==> Preparing: select * from test_table WHERE string = ?
[DEBUG] s.m.selectTest - ==> Parameters: hoge(String)
[DEBUG] s.m.selectTest - <== Total: 2
TestTable [id=1, string=hoge, number=100]
TestTable [id=2, string=hoge, number=200]
[DEBUG] s.m.selectTest - ==> Preparing: select * from test_table WHERE string = ? and number = ?
[DEBUG] s.m.selectTest - ==> Parameters: hoge(String), 200(Integer)
[DEBUG] s.m.selectTest - <== Total: 1
TestTable [id=2, string=hoge, number=200]
-
<where>
タグを使うと、子要素が何らかの文字列が存在した場合に限りWHERE
句を先頭に追加してくれるようになる。 - また、
<where>
タグ内の文字列がAND
かOR
で始まる場合、自動でその記述が除去される。 - この動きを
<if>
などだけで実現使用とすると、結構複雑な記述になってしまう。
trim タグで置き換える
上記の <where>
を使った定義は、 <trim>
タグを使って以下のように置き換えることができる。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<select id="selectTest" resultType="sample.mybatis.TestTable">
select *
from test_table
<trim prefix="where" prefixOverrides="AND|OR">
<if test="string != null">
and string = #{string}
</if>
<if test="number != null">
and number = #{number}
</if>
</trim>
</select>
</mapper>
-
prefix
属性に、先頭に追加する文字列。 -
prefixOverrides
属性に、先頭から除去する文字列をパイプ(|
)区切りで指定する。
set
test_table
package sample.mybatis;
public class TestTable {
private int id;
private String string;
private Integer number;
public TestTable id(int id) {
this.id = id;
return this;
}
public TestTable string(String string) {
this.string = string;
return this;
}
public TestTable number(int number) {
this.number = number;
return this;
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<update id="updateTest">
update test_table
<set>
<if test="string != null">
string = #{string},
</if>
<if test="number != null">
number = #{number},
</if>
</set>
where id = #{id}
</update>
</mapper>
package sample.mybatis;
import java.io.InputStream;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Main {
public static void main(String[] args) throws Exception {
try (InputStream in = Main.class.getResourceAsStream("/mybatis-config.xml")) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
try (SqlSession session = factory.openSession()) {
session.update("sample.mybatis.updateTest",
new TestTable().id(1).number(555));
session.update("sample.mybatis.updateTest",
new TestTable().id(3).string("update").number(999));
session.commit();
}
}
}
}
[DEBUG] s.m.updateTest - ==> Preparing: update test_table SET number = ? where id = ?
[DEBUG] s.m.updateTest - ==> Parameters: 555(Integer), 1(Integer)
[DEBUG] s.m.updateTest - <== Updates: 1
[DEBUG] s.m.updateTest - ==> Preparing: update test_table SET string = ?, number = ? where id = ?
[DEBUG] s.m.updateTest - ==> Parameters: update(String), 999(Integer), 3(Integer)
[DEBUG] s.m.updateTest - <== Updates: 1
test_table
-
<set>
タグを使うと、内部の文字列が空でない場合に、先頭にSET
句が追加される。 - また、末尾のカンマ(
,
)が自動で除去される。
trim タグで置き換える
<where>
のときと同じように、 <trim>
で置き換えることもできる。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<update id="updateTest">
update test_table
<trim prefix="set" suffixOverrides=",">
<if test="string != null">
string = #{string},
</if>
<if test="number != null">
number = #{number},
</if>
</trim>
where id = #{id}
</update>
</mapper>
-
suffixOverrides
属性で、末尾の削除する文字を指定する。
foreach
test_table
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<select id="selectTest" resultType="sample.mybatis.TestTable">
select *
from test_table
where id in
<foreach collection="list" item="item"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
</mapper>
package sample.mybatis;
import java.io.InputStream;
import java.util.Arrays;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Main {
public static void main(String[] args) throws Exception {
try (InputStream in = Main.class.getResourceAsStream("/mybatis-config.xml")) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
try (SqlSession session = factory.openSession()) {
session
.selectList("sample.mybatis.selectTest", Arrays.asList(1, 3, 5))
.forEach(System.out::println);
}
}
}
}
[DEBUG] s.m.selectTest - ==> Preparing: select * from test_table where id in ( ? , ? , ? )
[DEBUG] s.m.selectTest - ==> Parameters: 1(Integer), 3(Integer), 5(Integer)
[DEBUG] s.m.selectTest - <== Total: 3
TestTable [id=1]
TestTable [id=3]
TestTable [id=5]
-
<foreach>
タグを使用すると、コレクションを反復処理しながら SQL を構築できる。 -
collection
属性に、反復処理するコレクションを指定する。-
list
は、パラメータに反復処理可能なオブジェクトを渡したときにデフォルトで利用できる参照名(特殊なパラメータ名 を参照)。
-
-
item
属性で、反復中の各要素を参照するための一時変数名を宣言する。 -
open
属性は、反復の先頭に追加する文字列を指定する。 -
separator
属性は、各反復結果の間に挿入する文字列を指定する。 -
close
属性は、反復の最後に追加する文字列を指定する。 -
index
属性というのもあり、反復中のインデックス値を参照するための一時変数名を宣言できる。 - おもに
IN
句の動的生成に利用する。
bind
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
<select id="selectTest" resultType="sample.mybatis.TestTable">
<bind name="parameter" value="'@@@' + _parameter + '@@@'"/>
select *
from test_table
where value = #{parameter}
</select>
</mapper>
package sample.mybatis;
import java.io.InputStream;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Main {
public static void main(String[] args) throws Exception {
try (InputStream in = Main.class.getResourceAsStream("/mybatis-config.xml")) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
try (SqlSession session = factory.openSession()) {
session.selectList("sample.mybatis.selectTest", "abc");
}
}
}
}
[DEBUG] s.m.selectTest - ==> Preparing: select * from test_table where value = ?
[DEBUG] s.m.selectTest - ==> Parameters: @@@abc@@@(String)
-
<bind>
タグで、一時変数を定義できる。 -
name
属性で、一時変数の名前を定義する。 -
value
属性で、 OGNL 式を使った値の定義ができる。 -
_parameter
は、パラメータとして渡した値を参照できる暗黙変数(特殊なパラメータ名 を参照)。
プログラムで SQL を動的生成する
MyBatis にはアノテーションを使って SQL を定義する方法が用意されている。
しかし、文字列連結で SQL を組み立てていると、カンマ区切りとか AND
, OR
区切りなど、注意しないといけない細かい問題がいろいろある。
そのへんの面倒な部分を隠蔽した API が用意されている。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis.TestTableMapper"> <!-- ★Mapper を namespace に指定 -->
</mapper>
package sample.mybatis;
import java.util.List;
import org.apache.ibatis.annotations.SelectProvider;
// ★Mapper インターフェースをつくる
public interface TestTableMapper {
// ★@SelectProvider で SQL を生成するクラスとメソッドを設定する
@SelectProvider(type=TestTableSqlProvider.class, method="getSelectTestSql")
List<TestTable> selectTest();
}
package sample.mybatis;
import org.apache.ibatis.jdbc.SQL;
// ★SQL を生成するクラス。
public class TestTableSqlProvider {
// ★SQL を生成するメソッド
public String getSelectTestSql() {
// ★SQL クラスを使って、 SQL を生成する
SQL sql = new SQL() {{ // ★匿名クラスで生成
SELECT("id, hoge_value");
SELECT("fuga_value");
SELECT("piyo_value");
FROM("test_table");
}};
return sql.toString();
}
}
package sample.mybatis;
import java.io.InputStream;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class Main {
public static void main(String[] args) throws Exception {
try (InputStream in = Main.class.getResourceAsStream("/mybatis-config.xml")) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
try (SqlSession session = factory.openSession()) {
TestTableMapper mapper = session.getMapper(TestTableMapper.class);
mapper.selectTest();
}
}
}
}
[DEBUG] s.m.T.selectTest - ==> Preparing: SELECT id, hoge_value, fuga_value, piyo_value FROM test_table WHERE (hoge_value is not null)
[DEBUG] s.m.T.selectTest - ==> Parameters:
- Mapper のメソッドを
@SelectProvider
でアノテートする。-
type
に SQL を生成するクラスを、method
に SQL を生成するメソッドの名前を指定する。 -
INSERT
やUPDATE
には@InsertProvider
や@UpdateProvider
がある。
-
- SQL の生成には、
org.apache.ibatis.jdbc.SQL
クラスを使用する。 -
SQL
クラスを匿名クラスとして new して、インスタンス初期化ブロック内でSELECT()
やFROM()
メソッドを使って SQL を構築する。- 各引数には文字列で SQL の部品を渡す。
- この際、カンマ区切りなどは必要に応じて追加されたりする。
- 生成した
SQL
インスタンスのtoString()
メソッドで構築した SQL を文字列として取得できる。
各メソッドの使い方について
SELECT
new SQL() {{
SELECT("foo");
SELECT("bar");
SELECT("fizz, buzz");
FROM("foo_table");
}};
SELECT foo, bar, fizz, buzz FROM foo_table
- 連続して記述することで、各項目がカンマで勝手に区切られる。
- 明示的にカンマ区切りの文字列を渡してもOK。
- 末尾に不要なカンマがある場合は、除去してくれない。
FROM
new SQL() {{
SELECT("*");
FROM("foo_table");
FROM("bar_table");
}};
SELECT * FROM foo_table, bar_table
-
FROM
を連続させることで、カンマ区切りで FROM 句が列挙される。
WHERE
new SQL() {{
SELECT("*");
FROM("foo_table");
WHERE("foo = ?");
WHERE("bar = ?");
AND();
WHERE("fizz = ?");
WHERE("buzz = ?");
OR();
WHERE("hoge = ?");
WHERE("fuga = ?");
}};
SELECT *
FROM foo_table
WHERE (
foo = ?
AND bar = ?
) AND (
fizz = ?
AND buzz = ?
) OR (
hoge = ?
AND fuga = ?
)
-
WHERE()
メソッドでWHERE
句を生成できる。 - 連続させると
AND
条件で連結される。 -
AND()
およびOR()
メソッドでAND
,OR
で連結できる。
INNER_JOIN
new SQL() {{
SELECT("*");
FROM("foo_table");
INNER_JOIN("bar_table on bar_table.id = foo_table.bar_id");
INNER_JOIN("fizz_table on fizz_table.id = bar_table.fizz_id");
}};
SELECT *
FROM foo_table
INNER JOIN bar_table
on bar_table.id = foo_table.bar_id
INNER JOIN fizz_table
on fizz_table.id = bar_table.fizz_id
-
INNER_JOIN()
メソッドで、INNER JOIN
句を追加できる。 - 結合条件は頑張って文字列で組み立てないといけないっぽい。
ORDER_BY
new SQL() {{
SELECT("*");
FROM("foo_table");
ORDER_BY("foo");
ORDER_BY("bar desc");
}};
SELECT * FROM foo_table ORDER BY foo, bar desc
-
ORDER_BY()
メソッドでORDER BY
句を生成できる。 - 連続させることで、自動でカンマ区切りになる。
GROUP_BY
new SQL() {{
SELECT("foo, bar");
FROM("foo_table");
GROUP_BY("foo");
GROUP_BY("bar");
}};
SELECT foo, bar FROM foo_table GROUP BY foo, bar
-
GROUP_BY()
メソッドで、GROUP BY
句を生成できる。 - 連続させることで、自動でカンマ区切りになる。
HAVING
new SQL() {{
SELECT("foo, count(*)");
FROM("foo_table");
GROUP_BY("foo");
HAVING("0 < count(*)");
HAVING("count(*) < 100");
}};
SELECT foo
,count(*)
FROM foo_table
GROUP BY foo
HAVING (
0 < count(foo)
AND count(foo) < 100
)
-
HAVING()
メソッドでHAVING
句を生成できる。 -
WHERE()
と同じような動作をする(連続させるとAND
で連結される)。
INSERT_INTO, VALUES
new SQL() {{
INSERT_INTO("foo_table");
VALUES("foo_value", "#{foo_value}");
VALUES("fizz_value, buzz_value", "#{fizz_value}, #{buzz_value}");
}};
INSERT INTO foo_table (foo_value, fizz_value, buzz_value) VALUES (?, ?, ?)
-
INSERT_INTO()
メソッドでINSERT
文を生成できる。 -
VALUES()
メソッドで、登録する項目を定義できる。- 第一引数に値を設定する項目の名前を、
- 第二引数に
VALUES
句で指定する値を渡す。 - 複数記述すると、自動でカンマ区切りになる。
UPDATE, SET
new SQL() {{
UPDATE("foo_table");
SET("foo_value = #{foo_value}");
SET("bar_value = #{bar_value}");
WHERE("id = #{id}");
}};
UPDATE foo_table SET foo_value = ?, bar_value = ? WHERE (id = ?)
-
UPDATE()
メソッドで、UPDATE
文を生成できる。 -
SET()
メソッドで、SET
句を生成できる。- 連続させることで、自動でカンマ区切りになる。
DELETE_FROM
new SQL() {{
DELETE_FROM("foo_table");
WHERE("id = #{id}");
}};
DELETE FROM foo_table WHERE (id = ?)
-
DELETE_FROM()
メソッドでDELETE
文を生成できる。
Spring Boot と連携する
Spring Boot 用のプラグインが用意されているので、それを使えば簡単に連携できる。
test_table
|-build.gradle
`-src/main/
|-java/
| `-sample/mybatis/springboot/
| |-Main.java
| `-TestTableMapper.java
`-resources/
|-application.properties
|-mybatis-config.xml
`-TestTableMapper.xml
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'org.springframework.boot:spring-boot-gradle-plugin:1.3.1.RELEASE'
}
}
apply plugin: 'java'
apply plugin: 'spring-boot'
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
compileJava.options.encoding = 'UTF-8'
repositories {
mavenCentral()
}
dependencies {
compile 'org.springframework.boot:spring-boot-starter'
// ★MyBatis の SpringBoot 用プラグインを読み込む
compile 'org.mybatis.spring.boot:mybatis-spring-boot-starter:1.0.0'
compile 'mysql:mysql-connector-java:5.1.38'
}
# ★MyBatis の設定ファイルを読み込む
mybatis.config=mybatis-config.xml
# ★データソースの設定
spring.datasource.url=jdbc:mysql://localhost/test
spring.datasource.username=test
spring.datasource.password=test
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- ★データソースの管理は Spring Boot に任せるので、こちらには記述しない -->
<mappers>
<mapper resource="TestTableMapper.xml"/>
</mappers>
</configuration>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis.springboot.TestTableMapper">
<select id="selectTest" resultType="map">
select * from test_table
</select>
</mapper>
package sample.mybatis.springboot;
import java.util.List;
import java.util.Map;
public interface TestTableMapper {
List<Map<String, Object>> selectTest();
}
package sample.mybatis.springboot;
import java.io.IOException;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class Main {
public static void main(String[] args) throws IOException {
try (ConfigurableApplicationContext ctx = SpringApplication.run(Main.class, args)) {
Main m = ctx.getBean(Main.class);
m.method();
}
}
@Autowired
private SqlSessionTemplate sqlSession;
@Autowired
private TestTableMapper mapper;
public void method() throws IOException {
System.out.println("[SqlSessionTemplate]");
this.sqlSession
.selectList("sample.mybatis.springboot.TestTableMapper.selectTest")
.forEach(System.out::println);
System.out.println("[TestTableMapper]");
this.mapper
.selectTest()
.forEach(System.out::println);
}
}
[SqlSessionTemplate]
{id=1, value=hoge}
{id=2, value=fuga}
{id=3, value=piyo}
[TestTableMapper]
{id=1, value=hoge}
{id=2, value=fuga}
{id=3, value=piyo}
- Spring Boot で MyBatis を使う場合は、
mybatis-spring-boot-starter
を依存関係に追加する。 - 次に Spring Boot の設定ファイル(
application.properties
)にて、mybatis.config
という名前で MyBatis の設定ファイルを指定する。 - データソースは Spring Boot に管理してもらうので、 Spring Boot の方に設定を記述して、 MyBatis の方には記述しない。
-
SqlSession
にアクセスするには、SqlSessionTemplate
を@Autowired
でインジェクションすればいい。-
SqlSessionTemplate
の API はSqlSession
と同じ。 -
SqlSession
と違いスレッドセーフで、異なるスレッドや DAO から使っても問題ない。
-
- Mapper インターフェースを定義しておけば、
@Autowired
でインジェクションできる。- Spring にスキャンされる場所にインターフェースを入れておく必要がある。
-
Main
クラスを@MapperScan
でアノテートすれば、任意のパッケージの Mapper インターフェースをスキャンすることもできる(例:@MapperScan("foo.bar")
)。 - こちらもスレッドセーフなインスタンスがインジェクションされるため、異なるスレッドや DAO から利用しても問題ない。