1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

(Tips)Thymeleafとrowspanを使ったテーブル出力は、Mybatisのマッピングと相性がいい

Last updated at Posted at 2022-06-19

テーブルの複数行を束ねて表示したい場合

例えばこのような画面ですね(ω・

image.png

グループ番号の列を結合させたい場合に、Thymeleafでの記述はどうするのだろうか、で少し悩んだため残しておきます。

データとその構造

このテーブルの元データはデータベースにて以下の構成をしています。

image.png

テーブル名 説明
item 商品の情報を格納する。本サンプルでは簡単に商品番号と名前だけを格納している。
item_group 商品グループと商品の関連付けをする。1つのグループには1つ以上の商品を設定できる

item と item_group は item_id の値で関連付け(リレーション)を定義しています。具体的なデータ例は以下です。

itemテーブル

item_id name
1 商品A
2 商品B
3 商品C
4 商品D
5 商品E
6 商品F
7 商品G
8 商品H

item_groupテーブル

group_id item_id
1 1
2 2
2 3
3 4
3 5
3 6
3 7
3 8

データの親子関係(リレーション)を図で示すと、このようになるでしょう。

image.png

抽出するクエリ(SQL)

冒頭に示したデータを表示するクエリは次のとおりです。

SELECT 
	item_group.group_id
	, item.item_id
	, item.name
FROM 
	item 
INNER JOIN 
	item_group
	ON item.item_id = item_group.item_id 
ORDER BY item_group.group_id, item.item_id

検索結果をJavaのクラスへマッピングする

データベースからクラスへデータを格納します。
今回は Mybatis を使って、さらにデータの親子関係をもった状態で取得します。

マッピングするクラスは、データベースのテーブル定義とならって、ItemとItemGroupを作成します。
データの親子関係(リレーション)を維持した以下の状態になるようにクラスを構成します。

Item.java
package com.github.apz.model.item;

import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor @Getter
public class Item {
	private Integer itemId;
	private String name;
}

ItemGroup1つに対し1つ以上のItemがある、つまりItemGroupは複数のItemを持ちますので、java.util.List でItemを複数持つと宣言します。

ItemGroup.java
package com.github.apz.model.item;

import java.util.List;

import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor @Getter
public class ItemGroup {
	private Integer groupId;
	private List<Item> items;
}

検索結果を取得し、マッピングするMybatis用の定義xmlは以下です。

ItemMapper.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.github.apz.mapper.ItemMapper">
	<resultMap type="com.github.apz.model.item.ItemGroup" id="itemGroup">
		<result column="group_id" property="groupId"/>
		<collection property="items" ofType="com.github.apz.model.item.Item">
			<result column="item_id" property="itemId"/>
			<result column="name" property="name"/>
		</collection>
	</resultMap>
	
	<select id="findBy" resultMap="itemGroup">
		SELECT 
			item_group.group_id
			, item.item_id
			, item.name
		FROM 
			item 
		INNER JOIN 
			item_group
			ON item.item_id = item_group.item_id 
		ORDER BY item_group.group_id, item.item_id
	</select>
</mapper>

この検索結果を受け取るMapperインタフェースは以下です。複数のItemGroupを受け取ります。

ItemMapper.java
package com.github.apz.mapper;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;

import com.github.apz.model.item.ItemGroup;

@Mapper
public interface ItemMapper {
	List<ItemGroup> findBy();
}

この検索結果を 「そのままThymeleafへ渡し」ます。特殊な変換処理や、表示用のコード修正はしません。

Thymeleaf

複数の ItemGroup があり、さらに ItemGroup は複数のItemを持ちますので、繰り返し要素が2つあることになります。
先ほどの検索結果を itemGroups の名前で Thymeleaf テンプレートへ格納していた場合、次の手順で繰り返し出力します。

  • ItemGroup の数だけ繰り返し
  • Itemの数だけ繰り返し
    • Itemの先頭だけ、rowspan を出力し、その値は Itemの個数を設定する

これを実現するため、th:each を2階層出力しますが、Thymeleafは1つの要素に2つの th:eachは使えませんので(実行時エラー)、例えば以下のように1つ出力に影響のない要素に出力します。

item.html
<table class="table table-bordered table-striped table-hover">
	<thead>
		<tr>
			<th>グループ番号</th>
			<th>商品番号</th>
			<th>商品名</th>
		</tr>
	</thead>
	<tbody>
		<ins th:each="itemGroup: ${itemGroups}" th:remove="tag">
			<tr th:each="item: ${itemGroup.items}">
				<td th:if="${itemStat.first}" th:inline="text" th:rowspan="${itemStat.size}">[[${itemGroup.groupId}]]</td>
				<td th:inline="text">[[${item.itemId}]]</td>
				<td th:inline="text">[[${item.name}]]</td>
			</tr>
		</ins>
	</tbody>
</table>

itemStat: th:each で繰り返し出力する内容を item の名前で指定したとき、Thymeleafのデフォルト設定では、繰り返し要素の状態を扱う変数として、itemStat が利用できます。

  • 各Itemの先頭に group_id を出すので th:if="${itemStat.first}"
  • さらに rowspan に itemの数を指定するので、 th:rowspan="${itemStat.size}"

これで実現可能です。

まとめ

  • 複数の階層構造をもつデータをThymeleafで出力する方法を実現しました
  • MyBatisのマッピング結果で得たデータの階層構造をそのままThymeleafで使えることを示しました

サンプルコード全体

以下にMySQLでの動作サンプルを公開しています。

https://github.com/A-pZ/springboot-thymeleaf-rowspan-sample

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?