MyBatis を Spring Boot で利用するための基本的な設定およびサンプルコードをまとめます。サンプルコードにおいては、特に MySQL を対象とします。
公式の解説動画
サンプルプロジェクト構成
基本的な Gradle プロジェクトです。
.
|-- build.gradle
|-- gradle
| `-- wrapper
| |-- gradle-wrapper.jar
| `-- gradle-wrapper.properties
|-- gradlew
|-- gradlew.bat
`-- src
`-- main
|-- java
| `-- hello
| |-- Application.java
| |-- City.java
| |-- CityMapper.java
| `-- HelloController.java
`-- resources
|-- application.yml
|-- data.sql
`-- schema.sql
build.gradle
org.mybatis.spring.boot:mybatis-spring-boot-starter
のバージョンは spring-boot-gradle-plugin
によって自動解決されないため、こちらのページをもとに調べて指定する必要があります。今回は 1.3.0 を利用します。
buildscript {
ext {
springBootVersion = '1.5.3.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
jar {
baseName = 'gs-spring-boot'
version = '0.1.0'
}
repositories {
mavenCentral()
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
compile('mysql:mysql-connector-java:6.0.6')
// 以下のページをもとにバージョンを指定します。
// http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/
// http://search.maven.org/#artifactdetails|org.mybatis.spring.boot|mybatis-spring-boot-starter|1.3.0|jar
compile('org.mybatis.spring.boot:mybatis-spring-boot-starter:1.3.0')
}
src/main/resources/application.yml
MySQL DB 接続情報を設定します。
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: myuser
password: myuser
driver-class-name: com.mysql.jdbc.Driver
src/main/resources/{schema|data}.sql
出力先 MySQL テーブルの DDL および DML です。こちらのページに記載されているとおり、schema.sql および data.sql というファイル名の SQL がアプリケーション起動時に実行されます。より柔軟かつ高度な初期化処理が必要な場合は Flyway と連携するように設定します。
schema.sql
DROP TABLE IF EXISTS city;
CREATE TABLE city (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255),
state VARCHAR(255),
country VARCHAR(255)
);
data.sql
INSERT INTO city (name, state, country) VALUES ('San Francisco', 'CA', 'US');
src/main/java/hello/Application.java
package hello;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
src/main/java/hello/City.java
Getter/Setter が定義されたドメインクラスです。Lombok の @Data
などを利用すると簡潔な記述が可能になりますが、今回は使用していません。
package hello;
public class City {
private Long id;
private String name;
private String state;
private String country;
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getState() {
return this.state;
}
public void setState(String state) {
this.state = state;
}
public String getCountry() {
return this.country;
}
public void setCountry(String country) {
this.country = country;
}
@Override
public String toString() {
return getId() + "," + getName() + "," + getState() + "," + getCountry();
}
}
src/main/java/hello/CityMapper.java
@Param
アノテーションは、今回のようにパラメータが一つの場合は指定しなくても問題ありませんが、複数パラメータが存在する場合は SQL クエリ内のパラメータとメソッド引数の対応関係を設定するために指定する必要があります。また、ここでは @Select
アノテーションで SQL を設定していますが、より柔軟な設定を行うためには後述の XML ファイルを利用します。
package hello;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface CityMapper {
@Select("SELECT id, name, state, country FROM city WHERE state = #{state}")
City findByState(@Param("state") String state);
}
src/main/java/hello/HelloController.java
cityMapper.findByState()
および sqlSession.selectOne()
を利用する例です。実際にはコントローラ内で直接利用するのではなく、@Service や @Repository を設定したクラス内で利用します。
package hello;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@Autowired
private CityMapper cityMapper;
@Autowired
private SqlSession sqlSession;
@RequestMapping("/")
public String index() {
City city1 = cityMapper.findByState("CA");
City city2 = sqlSession.selectOne("hello.CityMapper.findByState", "CA");
System.out.println(city1);
System.out.println(city2);
return "Greetings from Spring Boot!";
}
}
XML による SQL 設定
サンプルプロジェクトにおいて、アノテーションで設定していた箇所を XML ファイルに置き換えると以下のようになります。
src/main/java/hello/CityMapper.java
package hello;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
//import org.apache.ibatis.annotations.Select;
@Mapper
public interface CityMapper {
// @Select("SELECT id, name, state, country FROM city WHERE state = #{state}")
City findByState(@Param("state") String state);
}
src/main/resources/hello/CityMapper.xml
<?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="hello.CityMapper">
<select id="findByState" resultType="hello.City">
SELECT * FROM city WHERE state = #{state}
</select>
</mapper>
SQL 設定のサンプル集
凡例
<?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="hello.CityMapper">
[[SQL 設定がここに入ります]]
</mapper>
適宜 @Param
設定を設定してください。返り値も適宜 List<City>
に変更してください。
package hello;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface CityMapper {
City findByState(@Param("name") String name, @Param("state") String state);
}
動的 SQL
if
<select id="findByState" resultType="hello.City">
SELECT * FROM city WHERE name LIKE #{name}
<if test="state != null">
AND state = #{state}
</if>
</select>
choose
いずれか一つだけ選択されます。switch-case 文のようなものです。
<select id="findByState" resultType="hello.City">
SELECT * FROM city WHERE
<choose>
<when test="name != null">
name = #{name}
</when>
<when test="state != null">
state = #{state}
</when>
<otherwise>
country = 'mycountry'
</otherwise>
</choose>
</select>
where
where
を利用すると、条件に一つも合致しない場合は何も挿入されず、合致した最初の条件が AND や OR から始まる場合は取り除かれます。以下の例で name も state も指定しないと city が全件返されます。state だけ指定すると AND が削除されて WHERE 句が挿入されます。
<select id="findByState" resultType="hello.City">
SELECT * FROM city
<where>
<if test="name != null">
name = #{name}
</if>
<if test="state != null">
AND state = #{state}
</if>
</where>
</select>
trim
where
のような機能をカスタマイズして実現したい場合は trim
を利用します。where
のサンプルコードを trim
で記述すると以下のようになります。
<select id="findByState" resultType="hello.City">
SELECT * FROM city
<trim prefix="WHERE" prefixOverrides="AND |OR ">
<if test="name != null">
name = #{name}
</if>
<if test="state != null">
AND state = #{state}
</if>
</trim>
</select>
set
UPDATE 文について、set
を利用すると合致した最後の条件が ,
で終わる場合は取り除かれます。where
の場合と同様に trim
で同等の機能を実現できます。
<update id="findByState">
UPDATE city
<set>
<if test="name != null">name=#{name},</if>
<if test="state != null">state=#{state}</if>
</set>
WHERE country = 'US'
</update>
foreach
<select id="findByState" resultType="hello.City">
SELECT * FROM city WHERE name IN
<foreach item="name" index="index" collection="names" open="(" separator="," close=")">
#{name}
</foreach>
</select>
bind
一時変数を定義できます。引数も参照できます。
<select id="findByState" resultType="hello.City">
<bind name="pattern" value="'%' + name + '%'" />
SELECT * FROM city WHERE name LIKE #{pattern}
</select>
静的 SQL
select
ここまでのサンプルで利用してきた、独自に定義した City 以外にも、基本的な resultType
が予め用意されています。例えば hashmap
を利用すると以下のようになります。
Mapper interface
Map<String, Object> findByState(@Param("state") String state);
XML
<select id="findByState" resultType="hashmap">
SELECT * FROM city WHERE state = #{state}
</select>
利用例
Map<String, Object> city = cityMapper.findByState("CA");
//→ {country=US, name=San Francisco, id=1, state=CA}
insert
Mapper interface
int insertCity(City city); // `id` 指定も可能ですが、無指定であれば自動で設定されます (後述)
XML
<insert id="insertCity" parameterType="hello.City" useGeneratedKeys="true" keyProperty="id">
INSERT INTO city (id, name, state, country) VALUES (#{id}, #{name}, #{state}, #{country})
</insert>
useGeneratedKeys
MySQL のAUTO_INCREMENT
などを利用したい場合keyProperty
設定したuseGeneratedKeys
の対象となる列名
利用例
cityMapper.insertCity(city); // INSERT された行数が返ります。
前述の foreach
と合わせて利用する例は以下のようになります。
Mapper interface
int insertCities(List<City> cities);
XML
<insert id="insertCities" parameterType="hello.City" useGeneratedKeys="true">
INSERT INTO city (id, name, state, country) VALUES
<foreach item="city" collection="cities" separator=",">
(#{city.id}, #{city.name}, #{city.state}, #{city.country})
</foreach>
</insert>
利用例
cityMapper.insertCities(cities); // INSERT された行数が返ります。
update
前述の set
を利用せずに update を記述すると以下のようになります。
Mapper interface
int updateCity(City city);
XML
<update id="updateCity" parameterType="hello.City">
UPDATE city SET
name = #{name},
state = #{state},
country = #{country}
WHERE id = #{id}
</update>
利用例
cityMapper.updateCity(city); // UPDATE された行数が返ります。
delete
Mapper interface
int deleteCity(City city);
XML
<delete id="deleteCity" parameterType="hello.City">
DELETE FROM city WHERE id = #{id}
</delete>
利用例
cityMapper.deleteCity(city); // DELETE された行数が返ります。
sql (部分的な SQL 文)
sql
を利用すると、SQL 文をパーツ化して使い回すことができます。例えば、以下のクエリは無理矢理三つの設定に分解できます。
<select id="findByState" resultType="hello.City">
SELECT * FROM city WHERE state = #{state}
</select>
↓
<sql id="my_refid1">
${my_prop1}
</sql>
<sql id="my_refid2">
FROM <include refid="${my_prop2}"/>
</sql>
<select id="findByState" resultType="hello.City">
SELECT *
<include refid="my_refid2">
<property name="my_prop1" value="city" />
<property name="my_prop2" value="my_refid1" />
</include>
WHERE state = #{state}
</select>
resultMap
resultMap
を利用すると、SQL の結果列と Java オブジェクトのプロパティの対応関係を設定できます。以下のような基本的な場合であれば自動で対応関係が解決されるため設定は不要ですが、無理矢理 name
列を name
, state
, country
プロパティに対応させるための設定を行いたい場合は以下のようになります。
<select id="findByState" resultType="hello.City">
SELECT * FROM city WHERE state = #{state}
</select>
↓
<resultMap id="cityResultMap" type="hello.City">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="state" column="name"/>
<result property="country" column="name"/>
</resultMap>
<select id="findByState" resultMap="cityResultMap">
SELECT * FROM city WHERE state = #{state}
</select>
resultMap を利用した JOIN クエリの記述の準備
resultMap
の主な用途は JOIN クエリの結果のマッピングです。
JOIN 対象となるデータの準備
schema.sql
DROP TABLE IF EXISTS city;
CREATE TABLE city (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255),
state VARCHAR(255),
country VARCHAR(255)
);
DROP TABLE IF EXISTS person;
CREATE TABLE person (
id INT PRIMARY KEY AUTO_INCREMENT,
city_id INT,
name VARCHAR(255)
);
data.sql
INSERT INTO city (id, name, state, country) VALUES (1, 'San Francisco1', 'CA1', 'US1');
INSERT INTO city (id, name, state, country) VALUES (2, 'San Francisco2', 'CA2', 'US2');
INSERT INTO person (city_id, name) VALUES (1, 'myname1');
INSERT INTO person (city_id, name) VALUES (2, 'myname2');
INSERT INTO person (city_id, name) VALUES (2, 'myname3');
結果の確認
mysql> select * from city;
+----+----------------+-------+---------+
| id | name | state | country |
+----+----------------+-------+---------+
| 1 | San Francisco1 | CA1 | US1 |
| 2 | San Francisco2 | CA2 | US2 |
+----+----------------+-------+---------+
2 rows in set (0.00 sec)
mysql> select * from person;
+----+---------+---------+
| id | city_id | name |
+----+---------+---------+
| 1 | 1 | myname1 |
| 2 | 2 | myname2 |
| 3 | 2 | myname3 |
+----+---------+---------+
3 rows in set (0.00 sec)
mysql> SELECT *
-> FROM
-> city AS c
-> LEFT JOIN person AS p ON c.id = p.city_id;
+----+----------------+-------+---------+------+---------+---------+
| id | name | state | country | id | city_id | name |
+----+----------------+-------+---------+------+---------+---------+
| 1 | San Francisco1 | CA1 | US1 | 1 | 1 | myname1 |
| 2 | San Francisco2 | CA2 | US2 | 2 | 2 | myname2 |
| 2 | San Francisco2 | CA2 | US2 | 3 | 2 | myname3 |
+----+----------------+-------+---------+------+---------+---------+
3 rows in set (0.00 sec)
ドメインモデル
既出の City に people
を追加しました。今回 Person を新規追加します。
City.java
package hello;
import java.util.List;
public class City {
private Long id;
private String name;
private String state;
private String country;
private List<Person> people; // 新規追加
public List<Person> getPeople() { // 新規追加
return this.people;
}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getState() {
return this.state;
}
public void setState(String state) {
this.state = state;
}
public String getCountry() {
return this.country;
}
public void setCountry(String country) {
this.country = country;
}
@Override
public String toString() {
return getId() + "," + getName() + "," + getState() + "," + getCountry() + "," + getPeople(); // 変更
}
}
Person.java
package hello;
public class Person {
private Long id;
private Long cityId;
private String name;
private City city;
public City getCity() {
return this.city;
}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public Long getCityId() {
return this.cityId;
}
public void setCityId(Long cityId) {
this.cityId = cityId;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return getId() + "," + getCityId() + "," + getName();
}
}
JOIN クエリの XML 設定例
Rails の ActiveRecord における「belongs_to
に相当する association
」と「has_many
に相当する collection
」のサンプル設定を記載します。
association (belongs_to)
<resultMap id="personResultMap" type="hello.Person">
<id property="id" column="person_id"/>
<result property="cityId" column="city_id"/>
<result property="name" column="person_name"/>
<association property="city" javaType="hello.City">
<id property="id" column="city_id"/>
<result property="name" column="city_name"/>
<result property="state" column="city_state"/>
<result property="country" column="city_country"/>
</association>
</resultMap>
<select id="findById" resultMap="personResultMap">
SELECT
p.id AS person_id,
p.name AS person_name,
c.id AS city_id,
c.name AS city_name,
c.state AS city_state,
c.country AS city_country
FROM
person AS p
LEFT JOIN city AS c ON p.city_id = c.id
WHERE p.id = #{id}
</select>
上記 resultMap
は分割して設定することができます。
<resultMap id="personResultMap" type="hello.Person">
<id property="id" column="person_id"/>
<result property="cityId" column="city_id"/>
<result property="name" column="person_name"/>
<association property="city" resultMap="cityResultMap"/>
</resultMap>
<resultMap id="cityResultMap" type="hello.City">
<id property="id" column="city_id"/>
<result property="name" column="city_name"/>
<result property="state" column="city_state"/>
<result property="country" column="city_country"/>
</resultMap>
collection (has_many)
<resultMap id="cityResultMap" type="hello.City">
<id property="id" column="city_id"/>
<result property="name" column="city_name"/>
<result property="state" column="city_state"/>
<result property="country" column="city_country"/>
<collection property="people" ofType="hello.Person">
<id property="id" column="person_id"/>
<result property="cityId" column="city_id"/>
<result property="name" column="person_name"/>
</collection>
</resultMap>
<select id="findByState" resultMap="cityResultMap">
SELECT
c.id AS city_id,
c.name AS city_name,
c.state AS city_state,
c.country AS city_country,
p.id AS person_id,
p.name AS person_name
FROM
city AS c
LEFT JOIN person AS p ON c.id = p.city_id
WHERE c.state = #{state}
</select>
実行例
cityMapper.findByState("CA0");
→ null
cityMapper.findByState("CA1");
→ 1,San Francisco1,CA1,US1,[1,1,myname1]
cityMapper.findByState("CA2");
→ 2,San Francisco2,CA2,US2,[2,2,myname2, 3,2,myname3]
MyBatis Generator (補足)
MyBatis の SQL 設定 XML ファイルの雛形を、DB のスキーマ情報から自動生成するプラグイン MyBatis Generator を利用すると、初期設定の手間が軽減されます。
build.gradle
buildscript {
ext {
springBootVersion = '1.5.3.RELEASE'
}
repositories {
mavenCentral()
// ここから追加
maven {
url 'https://plugins.gradle.org/m2/'
}
// ここまで追加
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
classpath('gradle.plugin.com.arenagod.gradle:mybatis-generator-plugin:1.4') // 追加
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'com.arenagod.gradle.MybatisGenerator' // 追加
jar {
baseName = 'gs-spring-boot'
version = '0.1.0'
}
repositories {
mavenCentral()
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
compile('mysql:mysql-connector-java:6.0.6')
compile('org.mybatis.spring.boot:mybatis-spring-boot-starter:1.3.0')
}
// ここから追加
configurations {
mybatisGenerator
}
mybatisGenerator {
verbose = true
configFile = 'src/main/resources/autogen/generatorConfig.xml'
}
// ここまで追加
src/main/resources/autogen/generatorConfig.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="MySQLTables" targetRuntime="MyBatis3">
<!-- スキーマ情報を取得する DB への接続設定 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mydb"
userId="myuser"
password="myuser">
</jdbcConnection>
<!-- SELECT 結果等を格納するドメインモデルを生成する設定 -->
<javaModelGenerator targetPackage="test.model" targetProject="./src">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- SQL 設定が記述された XML を生成する設定 -->
<sqlMapGenerator targetPackage="test.xml" targetProject="./src">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!-- マッパークラスを生成する設定 -->
<javaClientGenerator type="XMLMAPPER" targetPackage="test.dao" targetProject="./src">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!-- コードを生成するテーブルを指定 -->
<table schema="mydb" tableName="city">
<property name="useActualColumnNames" value="true" />
</table>
</context>
</generatorConfiguration>
実行例
./gradlew mbGenerator
結果ファイル
$ tree src/test/
src/test/
|-- dao
| `-- CityMapper.java
|-- model
| |-- City.java
| `-- CityExample.java
`-- xml
`-- CityMapper.xml
関連記事
- Spring Security フォームログインのサンプルコードSpring フレームワークによる Web アプリケーション開発で、ログイン処理を実装する際は Spring Security が便利です。ここでは特に Spring Boot で Web アプリケーションを開発する場合を対象とし、フォームによる ID/Password ログインを行うためのサンプルコードをまとめます。 公式ドキュメント [Spring Security チュートリアル](http...
- Java配列の宣言方法 (C/C++との違い)Javaの配列 Javaの配列宣言方法はC/C++と似ているようで若干異なる。 初期化しない場合 C/C++の int array[10]; はJavaでは int array[] = new int[10]; となる。同様にC/C++の int array[3][3]; はJavaでは int array[][] = new int[3][3]; となる。 初期化
- PlantUML による UML 図の描き方PlantUML はテキスト形式で表現されたシーケンス図やクラス図といった UML (Unified Modeling Language) 図の情報から画像を生成するためのツールです。簡単な使い方をまとめます。 インストール方法の選択 Atom や Eclipse のプラグインをインストールしてエディタから利用する方法、JAR をダウンロードして Java コマンドで実行する方法、Redmine ...
- Akka HTTP サンプルコード (Scala)Akka アクターを用いて実装された汎用 HTTP フレームワークです。Spray の後継です。コアモジュールである akka-http-core は 2016/2/17 に experimental が外れました。akka-http などのいくつかのサブモジュールは 2016/3/1 現在 experimental のままですが、基本的な
- Kestrel の使用例Kestrel は Message Queue (MQ) の実装のひとつです。一般に MQ はアプリケーション間やプロセス間、スレッド間で非同期に通信するために用いられます。メッセージの送信側は MQ に書き込めば受信側の応答を待たずに次の処理に非同期に進むことができます。Kestrel はわずか 2500 行程の Scala で実装されており JVM で動作します。MQ 自体はメモリ上に存在する...