프로젝트에서 친구 관계를 구현하는 역할을 맡았다.
친구 관계를 구현한 과정과, 그 과정에서 했던 고민들을 남겨두려 한다.
엔티티 설정
'친구'를 구현하기 위해서는 Member와 Member를 '다대다'로 설정해야 한다.
따라서 아래와 같이 `Friendship`이라는 중간 테이블을 사용할 것이다.
이때, `fromMember`, `toMember`, `isFriend` 필드를 주목하면 된다.
- `fromMember`: 친구 요청을 "보낸" 회원
- `toMember`: 친구 요청을 "받은" 회원
- `isFriend`: 서로 친구인지 판단하기 위한 필드
시도 1 - 친구 요청 시, 정방향 레코드만 저장
1번 유저와 2번 유저가 존재한다고 할 때, 1번 유저가 2번 유저에게 친구 요청을 보내는 상황이다.
그럼 아래와 같은 레코드가 저장될 것이다.
이번에는 더 복잡하게, 1,2,3,4,5 유저가 존재한다고 생각해보자.
1 <-> 2 : 1번이 2번에게 친구 요청을 보내고, 2번이 수락하여 1번과 2번이 친구인 상태
3 <-> 1 : 3번이 1번에게 친구 요청을 보내고, 1번이 수락하여 3번과 1번이 친구인 상태
1 -> 4: 1번이 4번에게 친구 요청을 보낸 상태
5 -> 1 : 5번이 1번에게 친구 요청을 보낸 상태
그럼 아래와 같은 레코드가 저장될 것이다.
이 상황에서 구현하고자 하는 기능은 세 가지이다.
1. 내가 친구 요청 보낸 회원 조회 기능
2. 나에게 친구 요청 보낸 회원 조회 기능
3. 나와 서로 친구인 회원 조회 기능
1. 내가 친구 요청 보낸 회원 조회 기능
내가 1번 사용자라고 할 때,` from_user_id`가 1 이면서 `is_friend`가 false 인 행의 `to_user_id`를 조회하면 된다.
2. 나에게 친구 요청 보낸 회원 조회 기능
내가 1번 사용자라고 할 때,`to_user_id`가 1 이면서 `is_friend`가 false 인 행의 `from_user_id`를 조회하면 된다.
3. 나와 서로 친구인 회원 조회 기능
문제는 여기서 발생한다.
내가 1번 사용자라고 할 때, 아래 표에서 나와 서로 친구인 회원은 2번과 3번이다.
하지만 2번은 1번이 친구 요청을 보냈기 때문에 `from_user_id`가 1번(=나)이고,
3번은 1번에게 친구 요청을 보냈기 때문에 `to_user_id`가 1번(=나)이다.
문제 1. 나와 친구인 회원을 조회하기 위해서는 from_user_id가 1인 행과 to_user_id가 1인 행을 모두 조회해야 한다.
문제 2. 이미 친구인 상황에서 역방향 요청 시에 요청이 성공한다.
문제 1번은 효율성이 떨어지지만 치명적인 문제는 아니다. 하지만 역방향 요청이 성공하는 것은 치명적인 문제이다.
따라서 다른 방법을 시도했다.
시도 2 - 친구 요청 시, 정방향 레코드와 역방향 레코드 저장
1번 유저와 2번 유저가 존재한다고 할 때, 1번 유저가 2번 유저에게 친구 요청을 보내는 상황이다.
그럼 아래와 같은 레코드가 저장될 것이다.
시도 1번에서는 친구 요청 시에, 아직 친구가 아니라는 의미에서 `is_friend`에 `false`를 집어넣었다.
하지만 시도2번에서는, 정방향 레코드의 `is_friend`에는 `true`를 집어넣는다.
이유는, 1번이 2번에게 요청한 것이라는 '방향성'을 표시하기 위함이다.
이전과 같이 1,2,3,4,5 유저가 존재하는 상황을 가정해보자.
1 <-> 2 : 1번이 2번에게 친구 요청을 보내고, 2번이 수락하여 1번과 2번이 친구인 상태
3 <-> 1 : 3번이 1번에게 친구 요청을 보내고, 1번이 수락하여 3번과 1번이 친구인 상태
1 -> 4: 1번이 4번에게 친구 요청을 보낸 상태
5 -> 1 : 5번이 1번에게 친구 요청을 보낸 상태
그럼 아래와 같은 레코드가 저장될 것이다.
1. 내가 친구 요청 보낸 회원 조회 기능
역방향 레코드를 이용하자.
(역방향 레코드를 이용하는 이유는, 정방향 레코드의 `is_friend`는 항상 `true`이기 때문이다.)
내가 1번 사용자라고 할 때, `to_user_id`가 1 이면서 `is_friend`가 `false`인 행의 `from_user_id` 조회를 조회하면 된다.
(실제로는 1번이 요청한 것이지만, 역방향 레코드를 이용할 것이기 때문에 `to_user_id`가 1인 행을 조회하는 것이다.)
2. 나에게 친구 요청 보낸 회원 조회 기능
위와 마찬가지로 역방향 레코드를 이용하자.
내가 1번 사용자라고 할 때, `from_user_id`가 1 이면서 `is_friend`가 false 인 행의 `to_user_id`를 조회하면 된다.
(실제로는 1번이 요청받은 것이지만, 역방향 레코드를 이용할 것이기 때문에 `from_user_id`가 1인 행을 조회하는 것이다.)
3. 나와 서로 친구인 회원 조회 기능
SELF JOIN을 이용하여 서로 친구인 회원을 조회할 수 있다.
+) SELF JOIN이란?
일반적인 JOIN처럼 두 개의 서로 다른 테이블 내에 있는 데이터를 하나의 테이블로 붙이는 대신, 자기 자신 안에 있는 똑같은 데이터를 가져다 붙이는 방식이다.
그렇다면 `FRIENDSHIP` 테이블을 SELF JOIN 해보자!
이 테이블에서 나와 서로 친구인 회원을 어떻게 조회할까?
위 쿼리문의 빨간색으로 표시한 부분처럼 WHERE 절을 이용해서 찾을 수 있다.
이때 `WHERE f.from_user_id = 1`에서 `1` 부분에는 로그인한 사용자의 id가 동적으로 들어가면 된다.
전체 과정을 요약하자면 아래와 같다.
1번과 2번 방법 모두 장단점이 존재하지만, 1번 방법의 역방향 요청이 성공한다는게 치명적인 단점이라 생각했기 때문에 2번 방법을 이용했다.
참고 자료
[DB] 친구 테이블 ERD 설계, 친구 목록 조회 기능 구현 (Querydsl 셀프 조인)
오라클 데이터베이스 SQL 강의/강좌 37강 - SELF JOIN
'✍️ 개발 기록' 카테고리의 다른 글
서버 성능 테스트란? (0) | 2023.12.18 |
---|---|
[🎵MIML] H2 DB를 이용하여 Mocking 없이 테스트 하기 (0) | 2023.11.10 |
[🌸다시, 봄] @ControllerAdvice를 이용하여 중앙 집중식으로 예외 처리하기 (0) | 2023.10.25 |
[🎵MIML] 서브모듈(Submodule)을 사용하여 개발 효율 개선하기 (0) | 2023.10.23 |
[🎵MIML] 리다이렉트 지옥 탈출한 썰..😵💫 (with. SessionCreationPolicy) (0) | 2023.10.20 |