📝 연관관계
- 구독하기 기능을 구현해보기 전에 먼저 연관관계부터 파악해보겠습니다.
- 한 명의 사용자는 여러 명의 사용자를 구독할 수 있으며, 한 명의 사용자는 여러 명으로부터 구독을 받을 수 있습니다. 즉, 둘의 관계(User, User)는 각각 1:N, N:1로 N:N이 됩니다.
- 다만 개체의 관계를 N:N 관계로 표현하는 것이 그렇게 좋은 방법은 아니므로 이를 1:N 두 개를 사용하여 구현하려고 합니다. 이를 자바 코드로 살펴보면 다음과 같습니다.
package com.cos.photogram.domain.subscribe;
...
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table( //데이터베이스에서 두 개의 컬럼에 대해 unique 제약조건 설정
uniqueConstraints = {
@UniqueConstraint(
name = "subscribe_uk",
columnNames = {"from_user_id", "to_user_id"}
)
}
)
@Entity
public class Subscribe {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
private User from_user;
@ManyToOne
private User to_user;
private LocalDateTime create_date;
@PrePersist
public void createDate() {
create_date = LocalDateTime.now();
}
}
- 해당 개체는 구독을 하는 사용자(
from_user
)와 구독을 받는 사용자(to_user
)로 컬럼이 구성되어 있으며 사용자가 똑같은 사용자에게 구독을 두 번 이상 할 수 없으므로 이를 제약조건으로 추가해주기 위해@Table
어노테이션을 사용하였습니다.
제약조건을 체크할 컬럼이 하나일 경우에는 그냥 unique = true 속성을 적용해주면 되지만, 위처럼 두 개 이상의 컬럼을 동시에 체크해야하는 경우에는 @Table 어노테이션을 활용할 수 있습니다.
📝 Repository
- 본격적으로 구독하기 기능을 구현하기에 앞서 Repository를 먼저 구현해보겠습니다.
package com.cos.photogram.domain.subscribe;
...
public interface SubscribeRepository extends JpaRepository<Subscribe, Long> {
@Modifying //INSERT, DELETE, UPDATE를 native query로 작성하려면 해당 어노테이션이 필요
@Query(value = "INSERT INTO subscribe(from_user_id, to_user_id, create_date) VALUES(:from_user_id, :to_user_id, now())", nativeQuery = true)
void Subscribe(Long from_user_id, Long to_user_id);
@Modifying
@Query(value = "DELETE FROM subscribe WHERE from_user_id = :from_user_id AND to_user_id = :to_user_id", nativeQuery = true)
void unSubscribe(Long from_user_id, Long to_user_id);
}
- 해당 Repository는 네이티브 쿼리를 사용하고 있으며 각각 데이터베이스에 INSERT, DELETE를 수행하고 있다. 이를 위해서는
@Modifying
어노테이션이 필요합니다.
📝 Service
- 다음으로 서비스 단에서는 위에서 구현한 Repository를 사용하여 구독을 수행합니다.
package com.cos.photogram.service;
...
@RequiredArgsConstructor
@Service
public class SubscribeService {
private final SubscribeRepository subscribeRepository;
@Transactional
public void subs(Long from_user_id, Long to_user_id) {
try {
subscribeRepository.Subscribe(from_user_id, to_user_id);
} catch(Exception e) {
throw new CustomApiException("구독하기 오류: 이미 구독한 상태입니다.");
}
}
@Transactional
public void unSubs(Long from_user_id, Long to_user_id) {
subscribeRepository.unSubscribe(from_user_id, to_user_id);
}
}
- 예외처리를 위해
try
,catch
문을 사용하고 있으며 구독취소의 경우 DELETE가 제대로 수행되지 않더라도 내부적으로 오류가 발생하지 않으므로 따로 처리해주지 않았습니다. 사용한CustomApiException
은 아래와 같이 구현되어 있습니다.
package com.cos.photogram.handler.ex;
public class CustomApiException extends RuntimeException {
//객체 구분에 사용
private static final long serialVersionUID = 1L;
public CustomApiException(String message) {
super(message);
}
}
- 기존에 구현해놓았던 Exception 클래스를 조금 수정하여 구현한 것입니다.
- 마찬가지로 Exception Handler에 해당 Exception을 캐치하기 위한 함수를 추가해주었습니다.
package com.cos.photogram.handler;
...
@RestController
@ControllerAdvice
public class ControllerExceptionHandler {
...
@ExceptionHandler(CustomApiException.class)
public ResponseEntity<?> apiException(CustomApiException e) {
return new ResponseEntity<>(new CMRespDto<>(-1, e.getMessage(), null), HttpStatus.BAD_REQUEST);
}
}
📝 Controller
- 마지막으로 컨트롤러에서는 위에 구현한 비즈니스 로직을 사용하여 구독하기, 구독취소를 수행합니다.
package com.cos.photogram.web.api;
...
@RequiredArgsConstructor
@RestController
public class SubscribeApiController {
private final SubscribeService subscribeService;
@PostMapping("/api/subscribe/{to_user_id}")
public ResponseEntity<?> subscribe(@AuthenticationPrincipal PrincipalDetails principalDetails, @PathVariable Long to_user_id) {
subscribeService.subs(principalDetails.getUser().getId(), to_user_id);
return new ResponseEntity<>(new CMRespDto<>(1, "구독하기 완료", null), HttpStatus.OK);
}
@DeleteMapping("/api/subscribe/{to_user_id}")
public ResponseEntity<?> unSubscribe(@AuthenticationPrincipal PrincipalDetails principalDetails, @PathVariable Long to_user_id) {
subscribeService.unSubs(principalDetails.getUser().getId(), to_user_id);
return new ResponseEntity<>(new CMRespDto<>(1, "구독취소 완료", null), HttpStatus.OK);
}
}
- 구독하기와 구독취소를 하는 사용자(
from_user
)는 현재 접속중인 사용자(principalDetails.getUser()
)가 될 것입니다.
📝 Result
- 이후 테스트 결과를 확인해보면 다음과 같습니다.
'🚗 Backend Toy Project > Photogram' 카테고리의 다른 글
[Photogram] 프로필 페이지 - DB에 이미지 업로드 (0) | 2022.07.11 |
---|---|
[Photogram] 프로필 페이지 - 서버에 이미지 업로드 (0) | 2022.07.10 |
[Photogram] 회원정보 수정 (0) | 2022.07.09 |
[Photogram] 로그인 (0) | 2022.07.08 |
[Photogram] 회원가입 - 공통 응답 DTO, Script 만들기 (0) | 2022.07.08 |