| |
Spring Boot+MyBatisを使用した開発手順
MyBatisを使用してSpring Boot Webアプリケーションからデータベースに接続する設定は、
複数の手順を踏んで設定する必要があるので一見大変そうですが、一つずつやれば難しくはありません。
[1] データベース接続情報をapplication.propertiesに記述
spring.datasource.url=jdbc:h2:C:/usr/dbms/h2/shiten/stdb;IFEXISTS=TRUE
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=dbuser1
spring.datasource.password=********
mybatis.configuration.map-underscore-to-camel-case=true
|
最後の行はMyBatis連携の設定の一つで、データベース表や列の名前を下線を含むタイプからキャメルケースタイプに自動変換してくれるオプションです。
例えば SHITEN_NAME という列名が shitenName というJava変数名に自動変換されます。
ちなみに、1つのSpringBoot Webアプリケーションから2つ以上のデータベースに接続するのは、
可能ですが設定が一気に複雑になるため、慎重に考慮して決める必要がありそうです。
[2] エンティティクラスを作る
ほとんどの場合、データベース上の表やビューと1対1に対応するエンティティクラスを作ることになると思います。
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class Shiten {
@NotBlank
@Size(min=1, max=5)
private String shitenCode;
private String shitenName;
private String address;
private String areaName;
private Integer areaCode;
@Max(999)
private Integer employeeNum;
}
|
アノテーションはSpring Boot Validationでバリデーションを掛けるためのもので、
MyBatisの動作には必須ではありません。
[3] Mapperインターフェースを作る
2種類の書き方が可能です。まず簡単なエンティティの場合、Mapperインターフェースの中にSQLも直接記述できます。
下記は全件を検索するメソッド1つ以外特にアクセスが不要な表なので、このクラス一つで完結させています。
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import jp.co.nsnhnkmmkk.testapp01.entity.Area;
@Mapper
public interface AreaMapper {
@Select("SELECT * FROM AREA ORDER BY AREA_CODE")
List<Area> selectAll();
}
|
アクセス(つまりCRUD)の種類が多いエンティティや、条件付きなど複雑なSQLを定義するエンティティの場合は、
下のようにJavaインターフェースは定義だけにしておき、SQLはMapper XMLに別途記述する方法が良いと思います。
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import jp.co.nsnhnkmmkk.testapp01.entity.Shiten;
import jp.co.nsnhnkmmkk.testapp01.form.SearchForm;
@Mapper
public interface ShitenMapper {
List<Shiten> selectAll();
Shiten selectByPrimaryKey(String shitenCode);
List<Shiten> selectByCondition(SearchForm form);
int insert(Shiten shiten);
int updateByPrimaryKey(Shiten shiten);
int deleteByPrimaryKey(String shitenCode);
}
|
[4] Mapper XML を作る
Mapperインターフェース内に@SelectなどのアノテーションでSQLを書かなかった場合、
別途Mapper XMLファイルの作成が必要です。
XMLファイルの置き場所はJavaインターフェースとパッケージ階層を合わせる必要があります。
例えばMapperインターフェースのパッケージが
「jp.co.nsnhnkmmkk.testapp01」ならば、XMLファイルはWebアプリケーションソースディレクトリのルートから見て、
src/main/resources/jp/co/nsnhnkmmkk/testapp01 というディレクトリに配置する必要があります。
通常Spring Initializrだと src/main/resources の下に自動生成されるディレクトリは
static と templates だけなので、ここで必要になるディレクトリは自分で手動で作ることになると思います。
<?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="jp.co.nsnhnkmmkk.testapp01.mapper.ShitenMapper">
<select id="selectAll" resultType="local.shitenapp02.entity.Shiten">
SELECT s.SHITEN_CODE, s.SHITEN_NAME, s.AREA_CODE, a.AREA_NAME, s.ADDRESS, s.EMPLOYEE_NUM
FROM SHITEN s
INNER JOIN AREA a ON (a.AREA_CODE = s.AREA_CODE)
ORDER BY s.SHITEN_CODE
</select>
<select id="selectByPrimaryKey" resultType="jp.co.nsnhnkmmkk.testapp01.entity.Shiten">
SELECT s.SHITEN_CODE, s.SHITEN_NAME, s.AREA_CODE, a.AREA_NAME, s.ADDRESS, s.EMPLOYEE_NUM
FROM SHITEN s
INNER JOIN AREA a ON (a.AREA_CODE = s.AREA_CODE)
WHERE SHITEN_CODE = #{shitenCode}
</select>
<select id="selectByCondition" resultType="jp.co.nsnhnkmmkk.testapp01.entity.Shiten">
SELECT s.SHITEN_CODE, s.SHITEN_NAME, s.AREA_CODE, a.AREA_NAME, s.ADDRESS, s.EMPLOYEE_NUM
FROM SHITEN s
INNER JOIN AREA a ON (a.AREA_CODE = s.AREA_CODE)
<where><!-- whereを使うと先頭の不要なANDを削除したうえでWHEREを付与してくれる -->
<if test="shitenName != null">
s.SHITEN_NAME LIKE #{shitenName} || '%'
</if>
<if test="areaCode != null">
AND s.AREA_CODE = #{areaCode}
</if>
<if test="employeeNumMin != null">
AND s.EMPLOYEE_NUM >= #{employeeNumMin}
</if>
<if test="employeeNumMax != null">
AND s.EMPLOYEE_NUM <= #{employeeNumMax}
</if>
</where>
ORDER BY s.SHITEN_CODE
</select>
<insert id="insert">
INSERT INTO SHITEN(SHITEN_CODE, SHITEN_NAME, AREA_CODE, ADDRESS, EMPLOYEE_NUM)
VALUES(#{shitenCode}, #{shitenName}, #{areaCode}, #{address}, #{employeeNum})
</insert>
<update id="updateByPrimaryKey">
UPDATE SHITEN
SET SHITEN_NAME = #{shitenName},
AREA_CODE = #{areaCode},
ADDRESS = #{address},
EMPLOYEE_NUM = #{employeeNum}
WHERE SHITEN_CODE = #{shitenCode}
</update>
<delete id="deleteByPrimaryKey">
DELETE FROM SHITEN WHERE SHITEN_CODE = #{shitenCode}
</delete>
</mapper>
|
この例のように、バインド変数、いわゆるプレースホルダーは #{変数名} の形で記述します。
またXMLなので >、< の不等号はエスケープする必要がある点に注意が必要な一方、
if要素を記述すると入力値の有無によってWHERE句の一部を付けたり外したりできる点は便利です。
一つ注意があります。上の例ではwhereの中の各条件が指定されているかを「areaCode != null」
のようにnullかどうかで判別していますが、一般にWebアプリケーションの入力フォームでは、
プロパティ変数のデータ型がStringで値が空欄の項目はnullではなく空文字列が入ります。
その場合は「areaCode != ''」と空文字列と比較する必要があります。あるいは
「areaCode != null and areaCode != ''」と両方と比較すると丁寧です。
[5] サービスクラスを作る
ここまでの設定をすれば、Controllerクラスから
「mapper.selectAll()」などと実行すればDBアクセスが記述できるのですが、
大規模なシステムだとサービスクラスを挟むことが多いと思います。
以下は簡単なサービスクラスの例ですが、この中にはSpringにもMyBatisにも依存する処理は一行も出てきません。
つまり純粋に環境を分離してDBアクセスのみテスト実行が可能になっています。
(というか、そのようになるようにサービスクラスを作るのだと思いますね)
ちなみにMapperのインスタンス変数はAutowiredな変数です。
@Autowiredではなく、@RequiredArgsConstructorを使うことでinjectしています。
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import jp.co.nsnhnkmmkk.testapp01.entity.Shiten;
import jp.co.nsnhnkmmkk.testapp01.form.SearchForm;
import jp.co.nsnhnkmmkk.testapp01.mapper.ShitenMapper;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
@Transactional
public class ShitenService {
// Autowiredな変数
private final ShitenMapper mapper;
// 全件取得
public List<Shiten> selectAll(){
return mapper.selectAll();
}
// 1件取得
public Shiten selectByPrimarykey(String shitenCode) {
return mapper.selectByPrimaryKey(shitenCode);
}
// 条件検索
public List<Shiten> selectByCondition(SearchForm form){
return mapper.selectByCondition(form);
}
// 登録
public int insert(Shiten shiten) {
return mapper.insert(shiten);
}
// 更新
public int updateByPrimaryKey(Shiten shiten) {
return mapper.updateByPrimaryKey(shiten);
}
// 削除
public int deleteByPrimaryKey(String shitenCode) {
return mapper.deleteByPrimaryKey(shitenCode);
}
}
|
[6] Controllerに追記
最後にControllerにDBアクセスを記述します。
以下の例ではServiceクラスをinjectしていますが、Mapperを直接呼び出す設計ならばMapperインターフェースをinjectします。
@Controller
@RequiredArgsConstructor
public class STNP021Controller {
// Autowiredな変数
private final ShitenService service;
@RequestMapping("/Search")
public String search(@ModelAttribute("ichiranSearchForm") STNP021Form form, Model model) {
List<Shiten> recordList = service.selectByCondition(form);
model.addAttribute("recordList", recordList);
return "resultView";
}
}
|

(first uploaded 2024/08/04 last updated 2025/03/31, URANO398)
|