springframeworkとmybatisでassociationを使用したマッピング

やりたいこと

  • associationを使ってみたい
  • 前回はresultMap / constructor を使ったマッピングをやってみたのでその続き

前提

  • コードは前回の記事で作ったものに変更を加えていく

実行環境

  • 前回と同じ

検証

DBテーブルの追加/更新

  • 部署を管理するための DIVISIONテーブルを追加
  • USERSテーブルにDIVISIONテーブルを参照するカラムを追加
--- a/sql/001_init_tables.sql
+++ b/sql/001_init_tables.sql
@@ -1,9 +1,22 @@
+CREATE TABLE DIVISION (
+  DIVISION_ID CHAR(2) NOT NULL,
+  DIVISION_NAME VARCHAR(32) NOT NULL,
+  PRIMARY KEY (DIVISION_ID)
+);
+
+INSERT INTO DIVISION (DIVISION_ID, DIVISION_NAME) VALUES ('10', '人事部');
+INSERT INTO DIVISION (DIVISION_ID, DIVISION_NAME) VALUES ('20', '営業部');
+INSERT INTO DIVISION (DIVISION_ID, DIVISION_NAME) VALUES ('30', '開発部');
+
 CREATE TABLE USERS (
   USER_ID VARCHAR(32) NOT NULL,
   FIRST_NAME VARCHAR(32) NOT NULL,
   FAMILY_NAME VARCHAR(32) NOT NULL,
-  PRIMARY KEY (USER_ID)
+  DIVISION_ID CHAR(2) NOT NULL,
+  PRIMARY KEY (USER_ID),
+  FOREIGN KEY (DIVISION_ID) REFERENCES DIVISION (DIVISION_ID)
 );
-INSERT INTO USERS (USER_ID, FIRST_NAME, FAMILY_NAME) VALUES ('hogefuga', 'ほげ', 'ふが');
-INSERT INTO USERS (USER_ID, FIRST_NAME, FAMILY_NAME) VALUES ('foobar', 'ふー', 'ばー');
+
+INSERT INTO USERS (USER_ID, FIRST_NAME, FAMILY_NAME, DIVISION_ID) VALUES ('hogefuga', 'ほげ', 'ふが', '10');
+INSERT INTO USERS (USER_ID, FIRST_NAME, FAMILY_NAME, DIVISION_ID) VALUES ('foobar', 'ふー', 'ばー', '20');

Javaクラス

Division / DivisionId クラスの追加

public class DivisionId {
  private final String id;

  public DivisionId(final String id) {
    this.id = id;
  }

  public String toString() {
    return id;
  }
}
public class Division {

  private final DivisionId divisionId;
  private final String divisionName;

  public Division(final DivisionId divisionId, final String divisionName) {
    this.divisionId = divisionId;
    this.divisionName = divisionName;
  }

  public String toString() {
    return String.format("Division {%s:%s}", divisionId, divisionName);
  }
}

UserクラスにDivisionクラスのフィールドを追加

--- a/core/src/main/java/com/github/nkiri/core/domain/model/user/User.java
+++ b/core/src/main/java/com/github/nkiri/core/domain/model/user/User.java
@@ -1,11 +1,15 @@
 package com.github.nkiri.core.domain.model.user;

+import com.github.nkiri.core.domain.model.division.Division;
+
 public class User {

   private final UserId userId;
   private final String firstName;
   private final String familyName;

+  private Division division;
+
   public User(final UserId userId, final String firstName, final String familyName) {
     this.userId = userId;
     this.firstName = firstName;
@@ -14,6 +18,7 @@ public class User {

   public String toString() {
     return String.format(
-        "User {userId: %s, firstName: %s, familyName: %s}", userId, firstName, familyName);
+        "User {userId: %s, firstName: %s, familyName: %s, division: %s}",
+        userId, firstName, familyName, division);
   }
 }

UserMapper.xmlの修正 (本題)

  • Userクラスのコンストラクタでセットしないフィールドに値をセットするときにassociationを使う
    • 今回でいうと User.divisionがそれ
  • 公式ドキュメントによると has-oneのリレーションシップを扱うものだとのこと
  • 使い方は resultMapと同じような感じ
  • <association>要素の select属性を使うこともできるけど、N+1問題が発生するので巨大な結果を取得するような場合は注意が必要そう
--- a/core/src/main/resources/com/github/nkiri/core/infrastructure/datastore/UserMapper.xml
+++ b/core/src/main/resources/com/github/nkiri/core/infrastructure/datastore/UserMapper.xml
@@ -4,10 +4,14 @@
 <mapper namespace="com.github.nkiri.core.infrastructure.datastore.UserMapper">
   <select id="get" resultMap="userResultMap">
     SELECT
-    USER_ID as userId
-    , FIRST_NAME as firstName
-    , FAMILY_NAME as familyName
-    FROM USERS
+    u.USER_ID as userId
+    , u.FIRST_NAME as firstName
+    , u.FAMILY_NAME as familyName
+    , d.DIVISION_ID as divisionId
+    , d.DIVISION_NAME as divisionName
+    FROM USERS u
+    JOIN DIVISION d
+    ON u.DIVISION_ID = d.DIVISION_ID
     <where>
       USER_ID = #{id}
     </where>
@@ -20,11 +24,23 @@
     </constructor>
   </resultMap>
 
+  <resultMap id="divisionIdMap" type="com.github.nkiri.core.domain.model.division.DivisionId">
+    <constructor>
+      <idArg column="divisionId" javaType="string"/>
+    </constructor>
+  </resultMap>
+
   <resultMap id="userResultMap" type="com.github.nkiri.core.domain.model.user.User">
     <constructor>
       <idArg resultMap="userIdResultMap" column="userId" javaType="com.github.nkiri.core.domain.model.user.UserId"/>
       <arg column="firstName" javaType="string"/>
       <arg column="familyName" javaType="string"/>
     </constructor>
+    <association property="division" javaType="com.github.nkiri.core.domain.model.division.Division">
+      <constructor>
+        <idArg column="divisionId" resultMap="divisionIdMap" javaType="com.github.nkiri.core.domain.model.division.DivisionId"/>
+        <arg column="divisionName" javaType="string"/>
+      </constructor>
+    </association>
   </resultMap>
 </mapper>