😀

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

2021/12/26に公開

#はじめに
今回の実装は、「ログインユーザーの保有する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

#環境

  • Spring2.5.5
  • gradle
  • MyBatis2.2.0
  • MySQL

#実装
##エンティティ

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;
	
}
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;
}
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

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

}
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

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

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

   <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で様々なサービスを作成できますので、その辺を自分も今後はマスターしていければと思います。

Discussion