#28 Spring Mybatisを利用したデータベース操作[5. テーブル結合 association]
今回はMybatisを用いてテーブルの結合を行います。
前提条件
この記事はSpringの最低限の知識が必要になります。
また、なるべく分かりやすく書くつもりですが、この記事の目的は自分の勉強のアウトプットであるため所々説明は省略します。
前回まで
前回はMybatisを利用してデータの検索、全クリアを行いました。
構築環境
-
各バージョン
Spring Boot ver 2.7.5
mybatis-spring-boot-starter ver 2.2.2
Model Mapper ver 3.1.0
jquery ver 3.6.1
bootstrap ver 5.2.2
webjars-locator ver 0.46
thymeleaf-layout-dialect ver 3.0.0
成果物
E-R図
E-R図は以下になります。
本来であればユーザー1人に対して必ず1店舗紐付くようにしているため本来のE-R図とは少し違いますが、現状はテーブル(M_USER)の店舗ID
のNULLを許容しているため曖昧な書き方としています。
今回行うこと
- 新たなエンティティクラス(Department.java)の追加
- 既存のエンティティクラス(MUser.java)とFormクラス(UserDetailForm.java)にDepartment.javaの定義を追加
- マッピング定義の追加
- HTML(detail.html)の修正
1. 新たなエンティティクラス(Department.java)の追加
package com.example.model;
import lombok.Data;
@Data
public class MDepartment {
private Integer departmentId;
private String departmentName;
}
2. 既存のエンティティクラス(MUser.java)とFormクラス(UserDetailForm.java)にDepartment.javaの定義を追加
package com.example.model;
import java.util.Date;
import lombok.Data;
@Data
public class MUser {
private String userId;
private String phoneNumber;
private Integer postalNumber1;
private Integer postalNumber2;
private String address;
private String userName;
private String password;
private Date birthday1;
private Date birthday2;
private Integer age;
private byte[] accountIcon;
private Integer gender;
/* MDepartmentの追加 */
private MDepartment mdepartment;
}
package com.example.form;
import java.util.Date;
import org.springframework.web.multipart.MultipartFile;
import com.example.model.MDepartment;
import lombok.Data;
@Data
public class UserDetailForm {
private String userId;
private String phoneNumber;
private Integer postalNumber1;
private Integer postalNumber2;
private String address;
private String userName;
private String password;
private Date birthday1;
private Date birthday2;
private Integer age;
private MultipartFile acocountIcon;
private Integer gender;
/* MDepartmentの追加 */
private MDepartment mdepartment;
}
3. マッピング定義の追加
<?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とxmlのマッピング -->
<mapper namespace="com.example.repository.UserMapper">
<!-- マッピング定義(ユーザー) -->
<resultMap type="com.example.model.MUser" id="muser">
<id column="user_id" property="userId"/>
<result column="phone_number" property="phoneNumber"/>
<result column="postal_number1" property="postalNumber1"/>
<result column="postal_number2" property="postalNumber2"/>
<result column="address" property="address"/>
<result column="user_name" property="userName"/>
<result column="birthday1" property="birthday1"/>
<result column="birthday2" property="birthday2"/>
<result column="age" property="age"/>
<result column="account_icon" property="accountIcon"/>
<association property="mdepartment" resultMap="department"></association>
</resultMap>
<!-- マッピング定義(店舗) -->
<resultMap type="com.example.model.MDepartment" id="department">
<id column="department_id" property="departmentId" />
<result column="department_name" property="departmentName" />
</resultMap>
<!-- ユーザー1件登録 -->
<insert id="insertOne">
<!-- テーブル(M_USER)の各項目 -->
insert into m_user(
user_id,
phone_number,
postal_number1,
postal_number2,
address,
user_name,
password,
birthday1,
birthday2,
age,
account_icon,
gender,
department_Id
)
<!-- エンティティクラスに定義した各項目 -->
values(
#{userId},
#{phoneNumber, jdbcType=VARCHAR},
#{postalNumber1, jdbcType=INTEGER},
#{postalNumber2, jdbcType=INTEGER},
#{address, jdbcType=VARCHAR},
#{userName},
#{password},
#{birthday1, jdbcType=DATE},
#{birthday2, jdbcType=DATE},
#{age, jdbcType=INTEGER},
#{accountIcon, jdbcType=BLOB},
#{gender, jdbcType=INTEGER},
#{departmentId, jdbcType=INTEGER}
)
</insert>
<!-- ユーザー情報全件取得(初期表示用) -->
<select id="findAllMUser" resultType="MUser">
SELECT * FROM M_USER
</select>
<!-- 店舗の情報全件取得 -->
<select id="findAllMDepartment" resultType="MDepartment">
SELECT * FROM M_DEPARTMENT
</select>
<!-- ユーザー情報取得(検索処理用) -->
<select id="searchMUser" resultType="MUser">
SELECT * FROM M_USER
<where>
<if test="userId != null and userId != ''">
user_id like '%' || #{userId} || '%'
</if>
<if test="phoneNumber != null and phoneNumber != ''">
and phone_number = #{phoneNumber}
</if>
<if test="birthday2 != null">
and TO_CHAR(birthday2, 'yyyy-MM-dd') = TO_CHAR(#{birthday2}, 'yyyy-MM-dd')
</if>
<if test="userName != null and userName != ''">
and user_name like '%' || #{userName} || '%'
</if>
<if test="age != null">
and age = #{age}
</if>
<if test="gender != 2">
and gender = #{gender}
</if>
</where>
</select>
<!-- ユーザー情報1件取得 -->
<select id="findOneMUser" resultMap="muser">
SELECT * FROM M_USER
LEFT OUTER JOIN M_DEPARTMENT
ON M_USER.DEPARTMENT_ID = M_DEPARTMENT.DEPARTMENT_ID
WHERE user_id = #{userId}
</select>
<!-- ユーザー情報1件更新 -->
<update id="updateOneMUser">
UPDATE M_USER SET password = #{password}, user_name = #{userName} WHERE user_id = #{userId}
<!--UPDATE M_USER SET password = #{user.password}, user_name = #{user.userName} WHERE user_id = #{user.userId}-->
</update>
<!-- ユーザー情報1件削除 -->
<delete id="deleteOneMUser">
DELETE FROM M_USER WHERE user_id = #{userId}
</delete>
</mapper>
店舗テーブル(M_DEPARTMENT)のマッピング定義を追加しました。
また、ユーザーテーブル(M_USER)のマッピング定義にassociationタグ
を追加しました。
associationタグを使うと、マッピング定義内に別のマッピング定義を入れることができます。
ただし、利用できるのは結合先のデータが1件の場合のみにしか使用できません。
<!-- マッピング定義(ユーザー) -->
<resultMap type="com.example.model.MUser" id="muser">
<id column="user_id" property="userId"/>
<result column="phone_number" property="phoneNumber"/>
<result column="postal_number1" property="postalNumber1"/>
<result column="postal_number2" property="postalNumber2"/>
<result column="address" property="address"/>
<result column="user_name" property="userName"/>
<result column="birthday1" property="birthday1"/>
<result column="birthday2" property="birthday2"/>
<result column="age" property="age"/>
<result column="account_icon" property="accountIcon"/>
<!-- 別のマッピング定義を設定 -->
<association property="mdepartment" resultMap="department"></association>
</resultMap>
<!-- マッピング定義(店舗) -->
<resultMap type="com.example.model.MDepartment" id="department">
<id column="department_id" property="departmentId" />
<result column="department_name" property="departmentName" />
</resultMap>
また左側完全結合にてM_USER
、M_DEPARTMENT
をDEPARTMENT_IDをキーにして結合します。
<!-- ユーザー情報1件取得 -->
<select id="findOneMUser" resultMap="muser">
SELECT * FROM M_USER
LEFT OUTER JOIN M_DEPARTMENT
ON M_USER.DEPARTMENT_ID = M_DEPARTMENT.DEPARTMENT_ID
WHERE user_id = #{userId}
</select>
4. HTML(detail.html)の修正
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout/layout}">
<head>
<title>ユーザー詳細</title>
<!-- CSS読込 -->
<link rel="stylesheet" th:href="@{/css/user/list.css}">
</head>
<body>
<div layout:fragment="content">
<div class="header border-bottom">
<h1 class="h2">ユーザー詳細</h1>
</div>
<form id="user-detail-form" method="post" th:action="@{/user/detail}" class="form-signup" th:object="${userDetailForm}">
<input type="hidden" th:field="*{userId}">
<!-- ユーザー詳細情報 -->
<table class="table table-striped table-bordered table-hover">
<tbody>
<tr>
<th class="w-25">ユーザーID</th>
<td th:text="*{userId}"></td>
</tr>
<tr>
<th>電話番号</th>
<td th:text="*{phoneNumber} == null ? '登録されていません' : *{phoneNumber}"></td>
</tr>
<tr>
<th>郵便番号</th>
<td th:text="*{postalNumber1} == null ? '登録されていません' : |*{postalNumber1} - *{postalNumber2}|"></td>
</tr>
<tr>
<th>パスワード</th>
<td>
<input type="text" class="form-control" th:field="*{password}"/>
</td>
</tr>
<tr>
<th>ユーザー名</th>
<td>
<input type="text" class="form-control" th:field="*{userName}"/>
</td>
</tr>
<tr>
<th>誕生日</th>
<td th:text="*{birthday2} == null ? '登録されていません' : *{#dates.format(birthday2, 'YYYY/MM/dd')}"></td>
</tr>
<tr>
<th>年齢</th>
<td th:text="*{age} == null ? '登録されていません' : *{age}"></td>
</tr>
<tr>
<th>性別</th>
<div th:switch="*{gender}">
<td th:case= 0 th:text='男性'>
<td th:case= 1 th:text='女性'>
<td th:case= "*" th:text='登録されていません'>
</div>
</tr>
<tr>
<th>店舗名</th>
<td th:text="*{mdepartment} == null ? '登録されていません' : *{mdepartment.departmentName}">
</td>
</tr>
</tbody>
</table>
<!-- ボタンエリア -->
<div class="text-center">
<!-- 更新ボタン -->
<button class="btn btn-danger" type="submit" name="update">更新</button>
<!-- 削除ボタン -->
<button class="btn btn-primary" type="submit" name="delete">削除</button>
</div>
</form>
</div>
</body>
</html>
<th>店舗名</th>
<td th:text="*{mdepartment} == null ? '登録されていません' : *{mdepartment.departmentName}">
</td>