SpringBoot × MyBatis中間テーブルからINNER JOINで値を取得する方法

#はじめに
今回の実装は、「**ログインユーザーの保有するN件のチャットルームを取得する**」という実装を行いました。

私の場合、roomsテーブルにはusersを参照する外部キーカラムがなく、中間テーブルであるroom_usersテーブルにroom_idカラムと、ログインユーザーのidが入るcurrent_user_idカラム、ログインユーザーとチャットしているユーザーのidが入るuser_idカラムをカラムとして設定しています。

##usersテーブル

|id(PK) | email  | password  | password_conf | name  | role  | created_at | updated_at |
|-------|--------|-----------|---------------|-------|-------|------------|------------|
|int    |varchar | varchar   |varchar        |varchar|varchar|Timestamp   |Timestamp   |

##roomsテーブル

|id(PK) | room_name  | created_at |
|-------|------------|------------|
|int    |varchar     |Timestamp   |

##room_usersテーブル

|id(PK) | room_id(FK)  | current_user_id(FK) | user_id  | created_at |
|-------|--------------|---------------------|----------|------------|
|int    |int           | int                 |int       |Timestamp   |

![test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/879778/79ddd496-72dd-58a7-08c1-7e6c77b0e7fe.png)

#環境
- Spring2.5.5
- gradle
- MyBatis2.2.0
- MySQL

#実装
##エンティティ
```java:MUser.java
@Data
public class MUser {

	private int id;
	private String name;
	private String email;
	private String password;
	private String passwordConfirmation;
	private String role;
	
	@DateTimeFormat(pattern = "yyyy_MM_dd HH:mm:ss")
	private LocalDateTime createdAt;
	
	@DateTimeFormat(pattern = "yyyy_MM_dd HH:mm:ss")
	private LocalDateTime updatedAt;
	
}
```

```java:MRoom.java
@Data
public class MRoom {

	private int id;
	private String roomName;
	
	@DateTimeFormat(pattern = "yyyy_MM_dd HH:mm:ss")
	private LocalDateTime createdAt;
	
	private List<TRoomUser> roomUserList;
}
```

```java:TRoomUser.java
@Data
public class TRoomUser {

	private int id;
	private int roomId;
	private int currentUserId;
	private int userId;
	
	@DateTimeFormat(pattern = "yyyy_MM_dd HH:mm:ss")
	private LocalDateTime createdAt;
	
}
```

ここでのポイントは`MRoomクラス`に定義している**`List<TRoomUser> roomUserList:`**です。
なぜなら、後ほどでてくるSQLの記述でJOINするため(アソシエーションを設定するため)に必要だからです。

そして、room_usersテーブルの親テーブルはroomsテーブルになる(1:多)ので、その実質クラスに関係性になるフィールドを設定しています。

##Mapper
```java:RoomMapper.java
@Mapper
public interface RoomMapper {
	
	/**ログインユーザーのチャットルーム複数件取得*/
	public List<MRoom> findLoginUserRooms(int currentUserId);

}
```
```java:RoomMapper.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とxmlのマッピング -->
<mapper namespace="com.example.demo.repository.RoomMapper">

	<!-- マッピング定義(rooms) -->
	<resultMap type="com.example.demo.entity.MRoom" id="room">
		<id column="id" property="id"></id>
		<result column="room_name" property="roomName"></result>
		<result column="created_at" property="createdAt"></result>
		<collection property="roomUserList" resultMap="roomUser"></collection>
	</resultMap>
	
	<!-- マッピング定義(room_user) -->
	<resultMap type="com.example.demo.entity.TRoomUser" id="roomUser">
		<id column="id" property="id"></id>
		<result column="room_id" property="roomId"></result>
		<result column="current_user_id" property="currentUserId"></result>
		<result column="created_at" property="createdAt"></result>
		<result column="user_id" property="userId"></result>
	</resultMap>

<!-- ログインユーザーのチャットルーム複数件取得 -->
	<select id="findLoginUserRooms" resultMap="room">
		select
			room_name
		from rooms r
		inner join room_users ru
			on r.id = ru.room_id
		inner join users u
			on ru.current_user_id = u.id
		where
			ru.current_user_id = #{currentUserId,jdbcType=INTEGER}
	</select>
	
</mapper>
```

ここでのポイントはxmlのマッピング定義部分です。
roomsテーブルのマッピング定義部分では、`collectionタグ`でアソシエーションを明示しています。

そして、`collectionタグ`でroom_usersテーブルを指定しるために、room_usersテーブルのマッピング定義も記述します。
そうすることで、JOINでテーブル結合を使用することができます。

次にSQL定義部分ですが、解説すると、今回の実装仕様は、「**ログインユーザーが保有するチャットルーム一覧を表示する**」なので、select文でroomsテーブルのroom_nameカラムを取得します。

その時の条件に「ログインユーザーが保有する」というものをSQLに加えるので、中間テーブルのcurrent_user_idとusersテーブルにidを参照する必要があります。

また、今回はroomsテーブルから参照するため、roomsテーブルからusersテーブルを参照するには中間テーブルを挟む必要があるので、それに合致するようにINNER JOINとON句で結合と結合条件を設定するようにします。

##Service
```java:RoomService.java
public interface RoomService {
	
	/**ログインユーザーのチャットルーム複数件取得*/
	public List<MRoom> getLoginUserRooms(@AuthenticationPrincipal UserDetailServiceImpll loginUser);
}
```

```java:RoomServiceImpl.java
@Service
public class RoomServiceImpl implements RoomService {

	@Autowired
	private RoomMapper mapper;

    /**
	 *ログインユーザーのチャットルーム複数件取得
	 */
	@Override
	public List<MRoom> getLoginUserRooms(@AuthenticationPrincipal UserDetailServiceImpll loginUser) {
		//ログインユーザーのユーザーIDを取得
		int currentUserId = loginUser.getUser().getId();
		
		return mapper.findLoginUserRooms(currentUserId);
	};
}
```

ここで重要になるのは、ログインユーザーのユーザーIDを取得することです。
これを引数に渡すことで、SQLのWHERE句に値が渡され、テーブルの参照が行われます。

##Controller
```java:RoomController.java
@Controller
@RequestMapping("/")
@Slf4j
public class RoomController {
	
	@Autowired
	private RoomService roomService;

	
	@GetMapping("/")
	public String getRoomsIndex(Model model, @AuthenticationPrincipal UserDetailServiceImpll loginUser) {
		
		//ログインユーザーのユーザー名取得
		String username = loginUser.getUser().getName();
		model.addAttribute("username", username);
				
		List<MRoom> rooms =roomService.getLoginUserRooms(loginUser);
		model.addAttribute("rooms", rooms);
		
		return "rooms/index";
	}
```
コントローラーでは一覧表示するため、List型の変数としてサービルロジックを定義して、Modelに登録することでビューに一覧を渡しています。

##View
```html
   <div class="rooms">
		<div class="room" th:each="room: ${rooms}">
			<div class="room-name">
				<a th:href="@{/messages}" th:text="${room.roomName}">techroom1</a>
			</div>
		</div>
	</div>
```

`th:each属性`で一覧を一つずつ表示できるので、そのようにして、一覧表示してあげます。

#まとめ
特に重要なのが、エンティティとMapper、SQLの部分になるので、テーブル同士のリレーションを考えつつサービスを作成しないといけないです。

若干難しい部分もありますが、SpringBootはテーブル設計をしっかりと行えば、オリジナリティの高いSQLで様々なサービスを作成できますので、その辺を自分も今後はマスターしていければと思います。