오늘 진행한 학습 요약
1. Tripf 프로젝트 중간 점검
- 진행 과정 정리
- 트러블 슈팅 내용
Tripf 프로젝트 Github : https://github.com/shine-idle/tripf
2. 복합키란?
- 복합키 정리
1. Tripf 프로젝트 중간 점검
진행 과정 정리
참고 사이트 : Tripf GitHub 프로젝트 backlog
- Github Project를 통해 Tripf 프로젝트의 전반적인 과정을 정리했다.
- 중간 전까지 대부분의 과정이 완료되었고 팀원들의 개발 경과를 확인할 수 있어서 좋았다.
트러블 슈팅 내용
- 🤔 문제
- 피드 - 일정 - 활동순으로 연관관계를 맺고 있는 상태에서 피드에 적힌 yyyy-mm-dd ~ yyyy-mm-dd 사이에만 일정을 추가할 수 있도록 하고 싶은데 yyyy-mm-dd와 yyyy-mm-ddThh:mm:ss 가 동일 일정임에도 추가할 수 없는 문제가 발생하였다.
- 😎 해결방법
- 날짜 검증 로직에서 LocalDate와 LocalDateTime 변환이슈로 인한 문제였다.
- 따라서 daysRequestDto.getDate()(일정 요청 Dto) 를 LocalDate로 변환하여 범위를 비교하였다.
- isBefore와 isAfter는 java.time.LocalDate, java.time.LocalDateTime에서 사용되는 메서드로 날짜 또는 날짜-시간의 순서를 비교하는 메서드로 시간을 비교하거나 확인할 때 좋다.
- 😶 고민
- isEqual()도 있는데 이 경우는 두 날짜가 동일한지도 비교가 가능하다. 하지만 동일한 일정을 추가하지 못하게 하는 로직에는 사용할 수 없었다. 이유는 데이터베이스에 동일한 값이 있는지에 대한 검증이 필요했기 때문에 isEqual()로는 메모리 내 객체가 아닌 데이터베이터 상태까지 검증할 수 없었다.
- 현재 내가 작성한 코드에 DB에 여러 번 접근하는 것 같아 추가 수정이 필요할 것 같다
- 🤔 문제
- Redis에서 LocalDateTime 직렬화 문제로 Spirng이 시작되지 않는 문제가 발생하였다.
- 😎 해결방법
- java.time.LocalDateTime을 Redis 캐시에 직렬화하는 과정에서 Jackson이 기본적으로 이를 지원하지 않아서 발생하는 문제였다.
- Redis 직렬화에서 LocalDateTime을 처리할 수 있도록 jackson 설정을 수정하였다.
- Redis 직렬화 설정을 Redis config 파일에 커스터마이징하였다.
- ObjectMapper에 JavaTimeModule을 등록하고 수정된 ObjectMapper을 사용하는 GenericJackson2JsonRedisSerializer를 적용하였다.
- LocalDateTime 등 java 8의 날짜/시간 API를 처리하려면 Jackson에 JavaTimeModule을 등록해야 한다.
- 😶 고민
- Redis 직렬화 문제에 대해서 조금 더 공부할 필요가 있을 것 같다!
- 캐시에 JavaDateTime을 저장하지 않아도 직렬화 문제가 발생했는데 이유가 뭔지 더 알아봐야 할 것 같다!
- 🤔 문제
- 피드에 일정 그리고 일정에 대한 활동을 피드 생성 시 모두 받고 싶은데 어떻게 처리해야 할지 고민이 있었다.
- Feed <- days <- Activity
- 😎 해결방법
- Feed, Days, Activity의 Dto와 Entity를 나누고 하위 엔티티에서 ManyToOne 설정을 한 뒤 Dto에서는 하위 객체를 List로 받아온다.
- 그리고 데이처를 계층적으로 반환하기 위해 Stream을 활용해 세 개의 Dto를 모두 반환했다.
- 😶 고민
- 그럼 이렇게 Dto를 여러 개 받는 경우에는 프론트에서는 어떻게 표현이 될지 궁금했다. 나눠서 받는 게 아니라 여러 번 값이 입력될 수 있게 되는 건가??
- 🤔 문제
- 알림 발생 시 알림 조회자는 확인이 가능하나 알림 발생자를 확인할 수 없는 문제가 발생했다.
- 알림 조회자는 DB에 User와의 연관관계로 저장하면 되는데 알림 조회 자는..?!!
- 이걸 생각 못했다니.. ㅠㅠ
- 😎 해결방법
- User에서 연관관계를 두 개 설정하면 된다!!!!!
- User Enitiy에서 Notification Entity와 두 개의 연관관계를 가지도록 설정하여 user(알림을 받는 사용자)와 actor(알림을 발생시킨 사용자)를 모두 저장할 수 있도록 수정하여 DB에 user와 actor를 모두 저장하였다.
- actor의 경우 알림이 필요한 메서드 안에 알림 발생 메서드와 연결해서 저장되도록 설정해 주었다
- 😶 고민
- /notification 으로 userId를 따로 받지 않았는데 /users/{userId}/notification으로 매개변수를 받았다면 알림 발생자와 알림 조회 자를 DB에 더 저장하기 수월했을 것 같다.
- 🤔 문제
- 메일 발송할 때 HTML에 이미지를 삽입해서 같이 보내고 싶은데
- 😎 해결방법
- 이미지가 프로젝트 내부에 있으므로 이미지 경로를 찾지 못한다. 따라서 addInline 메소드를 통헤 html안에 임베딩을 하여 발송하여야 한다.
- MimeMessageHelper 클래스의 addInline 메소드 사용하여 cid를 이용하여 이미지를 html안에 임베딩하여 발송하면 이미지가 메일에 같이 저장되어 발송된다!!
- 😶 고민
- 스프링에서 email을 보낼 때 CSS가 적용되지 않아 인라인으로 CSS를 작성해야만 했다.
- 이메일 클라이언트는 보안상의 이유로 외부 리소스(예: CSS 파일)를 차단한다고 한다.
- 프론트는 제대로 배워본 적이 없어서 잘 모르나 이런 곳에서도 헤맸었다 ㅎㅎ
2. 복합키란?
복합키 정리
- DB에 대해서 계속 정리하고 공부하고 있지만 여전히 개념이 헷갈린다.
- 팀원이 팔로우&팔로잉, 피드 좋아요 기능에 복합키를 사용하였다.
- 복합키란 무엇인가?!
- 복합키(Composite Key)는 데이터베이스 테이블에서 두 개 이상의 컬럼(column)을 조합하여 기본 키(Primary Key)로 설정한 것을 의미
- 복합키는 개별 컬럼만으로는 고유하지 않은 데이터를 조합해 유일성을 보장하는 데 사용
- Follow 테이블에서 팔로우 관계를 저장한다고 가정하면
- follower_id와 following_id를 조합하여 복합키로 설정
- 복합키를 사용하는 이유
- 데이터 무결성 보장
- 복합키를 사용하면 두 컬럼의 조합이 고유성을 가지도록 강제
- 이는 데이터 중복을 방지하고 논리적 오류를 줄이는 데 유용
- 테이블 구조 단순화
- 단일 컬럼 키를 생성하지 않고도 자연스럽게 고유 조건을 만족할 수 있는 경우, 복합키를 통해 불필요한 추가 컬럼(예: surrogate key)을 줄일 수 있음
- 관계 설정에 용이
- 복합키를 외래 키로 참조하면 관계형 데이터 모델에서 복잡한 연관 관계를 간단히 표현
- 특정 도메인에 적합
- 조합으로 유일성을 보장하는 데이터 모델에 적합
- 주문 상품 테이블(order_id, product_id), 학생 강의 테이블(student_id, course_id)
- 데이터 무결성 보장
- 팔로우 & 팔로잉에서 복합키를 사용하면 좋은 이유
- 팔로우 관계는 follower_id와 following_id의 조합으로만 고유성을 보장할 수 있음
- 동일한 사용자가 동일한 대상을 여러 번 팔로우하지 못하게 하려면 복합키가 필요
- 팔로우 관계를 나타내기 위해 복합키를 사용하면 별도의 id 컬럼(서로게이트 키)을 추가하지 않아도 데이터 무결성을 유지
- 외래 키 관계를 설정하기 쉬워 사용자의 팔로우 목록을 가져오는 쿼리를 간단히 작성 가능
- 팔로우 관계 자체가 follower_id → following_id로 표현되기 때문에, 복합키가 데이터 구조와 잘 맞음 이는 테이블 설계의 가독성과 직관성을 높임