1対多のテーブル結合を行うとSQLの結果としては、1側のテーブルの重複したデータと、多側のデータがフラットに返ってきてしまいます。
ただ、業務ロジックで扱うには階層が明確になっていた方が良いことがあり、
階層化するためにループでグルーピングしてーという処理が必要になってしまいます。。。
と思っていたら、MyBatis側でこんなことしなくて機能が提供されていました!
目次
前提
以下のテーブル構成でやってみます!
CREATE TABLE IF NOT EXISTS TODO_LIST (
id VARCHAR(10) NOT NULL,
title VARCHAR(10),
created_by VARCHAR(10), -- 作成者
created_at TIMESTAMP, -- 作成日時
PRIMARY KEY(id)
);
CREATE TABLE IF NOT EXISTS TODO_ITEM_COMMENT (
todo_list_id VARCHAR(10) NOT NULL,
comment_id VARCHAR(10) NOT NULL,
comment VARCHAR(100),
PRIMARY KEY(todo_list_id, comment_id)
);
TODOリストと、リストのアイテムそれぞれにコメントが複数件紐づくみたいなイメージです!
目標はこんなEntityクラスで結果を受け取ること
public class TodoList {
private String title;
private String id;
private String updatedBy;
private List<TodoItemComment> comments;
}
public class TodoItemComment {
private String todoListId;
private String commentId;
private String comment;
}
実現方法
Mybatisのマッパー向けのxmlに <resultMap>
という定義をすることで実現できます!
以下<resultMap>
含めた全文です。
<?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="hirabay.mybatis.mapper.TodoListMapper">
<resultMap id="todoListWithComment" type="hirabay.mybatis.entity.TodoList" autoMapping="true">
<id column="id" property="id" />
<collection property="comments" ofType="hirabay.mybatis.entity.TodoItemComment" autoMapping="true"/>
</resultMap>
<select id="findByIdWithComment" resultMap="todoListWithComment">
select tl.id, tl.title, tic.todo_list_id, tic.comment_id as comment_id, tic.comment
from todo_list tl
left join todo_item_comment tic on tic.todo_list_id = tl.id
where tl.id = #{id}
</select>
</mapper>
ポイント
- <id>でグルーピングするカラムを指定する
- ここでは
ID
カラムを指定しているため、IDの値が同じだと同じグループだと認識して処理が実行される
- ここでは
- <collection>でリストで受け取りたいクラスを指定する
autoMapping="true"
変に変換が必要なケースをのぞいて、この設定をしておくと勝手にカラムとフィールドをマッピングしてくれる
結果波こんな感じ
{
"title": "title1",
"id": "1",
"updatedBy": null,
"comments": [
{
"todoListId": "1",
"commentId": "1",
"comment": "comment1-1"
},
{
"todoListId": "1",
"commentId": "2",
"comment": "comment1-2"
},
{
"todoListId": "1",
"commentId": "3",
"comment": "comment1-3"
}
]
}
おまけ(1対1の場合)
こんなステータス管理するテーブルがあるとします(TODO_LISTと一緒でいいじゃんとかはご容赦を〜)
CREATE TABLE IF NOT EXISTS TODO_LIST_STATUS (
id VARCHAR(10) NOT NULL,
status VARCHAR(10),
PRIMARY KEY(id)
);
これを以下のEntityで受け取りたいとします
public class TodoList {
private String title;
private String id;
private String updatedBy;
private TodoListStatus status;
}
public class TodoListStatus {
private String id;
private String status;
}
そんなときは <association>
を使えばOK!
<resultMap id="todoListWithComment" type="hirabay.mybatis.entity.TodoList" autoMapping="true">
<id column="id" property="id" />
<association property="status" javaType="hirabay.mybatis.entity.TodoListStatus" autoMapping="true"/>
</resultMap>
ポイント
- propertyにEntityクラスのフィールド名を指定する
- javaTypeにEntityクラスを指定する
autoMapping="true"
をつけておけば自動でカラムとフィールドのマッピングをしてくれます