Prev
概要
- 業務でMyBatisを使用することがあったので理解度向上のために自宅でも色々触ってみる
- 下記のような操作が可能なアプリを作成する
ソースコード
ユーザ一覧取得
- localhost:8080/allUsersへアクセスするとDBに登録しているデータを全件表示できるよう実装する
- 処理の流れは下記のイメージ
- ユーザがlocalhost:8080/allUsersへアクセス
- コントローラ → モデル → マッパー → XMLに記載したSQL実行 → 取得したデータをエンティティへ格納 → エンティティからDTOへ変換 → コントローラ → ビューに取得したデータを表示
データ取得用のSQL用意
- DBからデータを取得する際はSQLをアノテーションに記載する方法とXMLに記載する方法の2種類がある
- 今回はXMLにSQLを記載する方法を選択
UserMapper.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="com.example.mybatis.mapper.UserMapper">
<!-- 全ユーザを取得 -->
<select id="findAll" resultType="com.example.mybatis.entity.UserEntity">
SELECT
*
FROM
users
</select>
</mapper>
-
UserMapper.xml
のselectタグの値とUserMapper.java
のメソッド名を一致させることで、メソッドが呼ばれるとSQLを実行することが可能になる - resultType属性の値にはSQLを実行して取得したデータを格納するクラス(
UserEntity.java
)を指定- データ格納用のクラスにはDBのカラムに対応するフィールドを用意する
- 今回はUSERSテーブルで定義している下記の4つのカラムに対応するフィールドを用意
- id
- username
- age
- address
マッパー(DAO)インタフェースの実装
- 上記のXML内のSQLを呼び出すために
UserMapper
インタフェースを用意する
UserMapper.java
package com.example.mybatis.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import com.example.mybatis.dto.UserDto;
@Mapper
public interface UserMapper {
/**
* 全データを取得
*/
List<UserEntity> findAll();
}
-
@Mapper
でマッパーということを明示 -
findAll
メソッドによってUserMapper.xml
のselect文を呼び出す
モデル
- データベースとのやり取りを実施する
UserLogic
クラスを作成
UserLogic.java
package com.example.mybatis.model;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.example.mybatis.dto.UserDto;
import com.example.mybatis.mapper.UserMapper;
@Component
public class UserLogic {
@Autowired
private UserMapper userMapper;
/**
* findAllメソッドを呼び出し
* USERSテーブルの全てのデータを取得
*/
public List<UserDto> findAll() {
List<UserEntity> userEntities = userMapper.findAll();
return toDto(userEntities);
}
/**
* UserEntityからUserDtoに変換
*/
public List<UserDto> toDto(List<UserEntity> userEntities) {
List<UserDto> allUsers = new ArrayList<>();
for (UserEntity entity: userEntities) {
UserDto userDto = new UserDto();
userDto.setId(entity.getId());
userDto.setUsername(entity.getUsername());
userDto.setAge(entity.getAge());
userDto.setAddress(entity.getAddress());
allUsers.add(userDto);
}
return allUsers;
}
}
-
@Autowired
によってuserMapperに対してDIを実施 -
userMapper
インタフェースのfindAllメソッドを呼び出し - returnする際はListを
UserEntity
型からUserDto
型に変換する必要があるため、toDto
メソッドを呼び出して変換を実施
コントローラ
- モデル層からSQL実行結果を格納したデータを受け取りビューへ渡す処理を記述
MybatisController
@Controller
public class MybatisController {
@Autowired
private UserLogic userLogic;
// 中略
@GetMapping("/users")
public String allUsers(Model model) {
List<UserDto> allUserList = userLogic.findAll();
model.addAttribute("users", allUserList);
return "users";
}
- localhost:8080/allUsersへアクセスした際にallUsersメソッドを実行
- モデル(
UserLoic.java
)のfindAllメソッドを呼び出し、DBに登録されている全データをList型のallUserList
に格納 - 受け取った全データのリストをaddAttributeメソッドにより
users
という名前でビューに渡す - usersをreturnすることで
user.html
を表示
エンティティの作成
- SQLを実行し取得したデータを格納するためのエンティティクラスを作成
UserEntity.java
package com.example.mybatis.entity;
import lombok.Data;
@Data
public class UserEntity {
private Integer id;
private String username;
private Integer age;
private String address;
}
- USERSテーブルのカラムに対応するフィールドを宣言
-
@Data
によってgetterとsetterを自動生成
DTO作成
- コントローラとモデル間のデータのやり取りはこのDTOクラスを介して行うことで効率的なデータ受け渡しを実現
UserDto.java
package com.example.mybatis.dto;
import lombok.Data;
@Data
public class UserDto {
private Integer id;
private String username;
private Integer age;
private String address;
}
- 基本的にエンティティと同様にDBのカラムに対応するフィールドとgetter、setterを持つ
application.ymlの修正
-
application.yml
にもMyBatis関連の設定を追記
application.yml
mybatis:
mapper-locations: classpath:mapper/*.xml
-
mapper-location
でマッパーのXMLを配置しているパスを指定- 今回は
src/main/resources/mapper
に配置
- 今回は
ビューの作成
- 取得したデータを一覧で表示するビューを作成する
users.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>ユーザ一覧</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-3">
<h1>ユーザ一覧</h1>
<table class="table table-striped table-bordered">
<thead class="table-dark">
<tr>
<th>ID</th>
<th>ユーザ名</th>
<th>年齢</th>
<th>住所</th>
</tr>
</thead>
<tbody>
<!-- users からデータを繰り返し表示 -->
<tr th:each="user : ${users}">
<td th:text="${user.id}">ID</td>
<td th:text="${user.username}">ユーザ名</td>
<td th:text="${user.age}">年齢</td>
<td th:text="${user.address}">住所</td>
</tr>
</tbody>
</table>
<a th:href="@{/main}" class="btn btn-secondary mt-3">メイン画面へ戻る</a>
</div>
</body>
</html>
-
th:each
の部分によってコントローラから受け取った全ユーザのリストusers
をuser
として扱い、テーブルとして表示 - 表の見た目はBootstrapにより整えているが本記事の内容とはあまり関係ないため割愛
- メイン画面へ戻るためのボタンも用意
ユーザ一覧取得
ユーザ検索
- localhost:8080/userSearchへアクセスしIDを入力すると、該当するIDのユーザ情報を表示できるようにする
- 上記で作成したソースに対してそれぞれ下記の通り追記する
UserMapper.xm
UseMapper.xml
<select id="findUser" resultType="com.example.mybatis.entity.UserEntity">
SELECT
*
FROM
users
WHERE
id = #{id}
</select>
- 入力フォームから渡されたIDを受け取りSELECT文によりユーザ情報を抽出
UserMapper.java
UserMapper.java
/**
* 入力フォームで指定したIDのデータを取得
*/
UserEntity findUser(int id) throws UserNotFoundExeption;
-
findUser
メソッドの引数としてIDを渡す - 指定したIDに対応するユーザが存在しない場合は
UserNotExeption
を投げる
※後ほど作成
UserLogic.java
UserLogic.java
/**
* findUserメソッドを呼び出し
* URLで指定したIDのデータを取得
*/
public UserDto findUser(UserDto userDto) throws UserNotFoundExeption {
UserEntity userEntity = userMapper.findUser(userDto.getId());
if (userEntity == null) {
throw new UserNotFoundExeption("指定したユーザは存在しません。");
}
return toDto(userDto, userEntity);
}
/**
* UserEntityからUserDtoに変換
*/
public UserDto toDto(UserDto userDto, UserEntity userEntity) {
userDto.setUsername(userEntity.getUsername());
userDto.setAge(userEntity.getAge());
userDto.setAddress(userEntity.getAddress());
return userDto;
}
-
findUser
メソッドでは入力フォームで入力されたIDを受け取るために、引数としてuserDto
を指定-
userDto.getId()
でURLに入力したIDを取得可能
-
- 全件取得の際と同様に
UserEntity
型からUserDto
型へ変換しreturnする - 指定したIDに対応するユーザが存在しない場合は、エラーメッセージと共に
UserNotExeption
をコントローラへ投げる
MybatisController.java
MybatisController.java
UserDto userDto = new UserDto();
/**
* ユーザ検索画面表示
*/
@GetMapping("/userSearch")
public String userSearch() {
return "search/userSearch";
}
/**
* 検索結果表示
*/
@GetMapping("/userInfo")
public String userInfo(@RequestParam("id") int id, Model model) {
try {
userDto.setId(id);
userDto = userLogic.findUser(userDto);
model.addAttribute("user", userDto);
} catch (UserNotFoundException e) {
model.addAttribute("errorMessage", e.getMessage());
return "search/userInfo";
}
return "search/userInfo";
}
- localhost:8080/userSearchにアクセスするとユーザ検索画面を表示
- 検索ボタンを押下するとlocalhost:8080/userInfoへ遷移
-
@RequestParam("id")
により入力されたIDを引数として受け取る
-
- DTOの
id
に入力されたIDをセットしモデル層へ渡す - モデルから受け取ったSELECT文の実行結果(
userDto
)をuser
という名前でビューへ渡す - モデルから
UserNotFoundExeption
が返ってきている場合はエラーメッセージをビューへ渡す
userSearch.html
- ユーザ検索画面
user.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>ユーザ検索</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-3">
<h1>ユーザ検索</h1>
<form th:action="@{/userInfo}" method="get">
<div class="mb-3">
<label for="userId" class="form-label">ユーザID</label>
<input type="number" class="form-control" id="userId" name="id" required>
</div>
<button type="submit" class="btn btn-primary">検索</button>
</form>
<a th:href="@{/main}" class="btn btn-secondary mt-3">メイン画面へ戻る</a>
</div>
</body>
</html>
- th:actionではボタンを押した際の遷移先を指定
- 今回はlocalhost:8080/userInfoに遷移するため
userInfo
を指定
userInfo.html
- 検索結果表示画面
userInfo.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>ユーザ検索</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-3">
<h1>検索結果</h1>
<!-- 指定したIDのユーザが存在しない場合 -->
<div th:if="${errorMessage}">
<p th:text="${errorMessage}">指定したユーザは存在しません。</p>
</div>
<!-- 指定したIDのユーザが存在する場合 -->
<div th:if="${user}">
<table class="table table-striped table-bordered">
<thead class="table-dark">
<tr>
<th>ID</th>
<th>ユーザ名</th>
<th>年齢</th>
<th>住所</th>
</tr>
</thead>
<tbody>
<!-- user のデータを表示 -->
<td th:text="${user.id}">ID</td>
<td th:text="${user.username}">ユーザ名</td>
<td th:text="${user.age}">年齢</td>
<td th:text="${user.address}">住所</td>
</tbody>
</table>
</div>
<a th:href="@{/main}" class="btn btn-secondary mt-3">メイン画面へ戻る</a>
</div>
</body>
</html>
-
th:if
により指定したIDに対応するユーザが存在する場合と存在しない場合の条件分岐を実施 - 存在する場合はユーザ情報を、存在しない場合はエラーメッセージを表示
UserNotFoundExeption.java
- exeptionパッケージを新たに作成し
UserNotFoundExeption
クラスを作成
UserNotFoundExeption.java
package com.example.mybatis.exception;
/**
* ユーザが見つからない場合の例外
*/
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
-
ObjectNotFoundExeption
を使用しても良かったが、エラーメッセージに余計な文章がついてしまうため独自クラスとして実装 -
UserLogic
クラスで指定したメッセージをエラーメッセージとして設定
ユーザ検索
ユーザが存在する場合
- ID:1
ユーザが存在しない場合
- ID:7
Next