2
1

More than 1 year has passed since last update.

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

Posted at

はじめに

今回の実装は、「ログインユーザーの保有する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で様々なサービスを作成できますので、その辺を自分も今後はマスターしていければと思います。

2
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
2
1