728x90
320x100
✅ 연관관계
- N : 1 관계에서는 Fk는 Many 쪽에 둔다
- N : N 의 관계에서는 중간 테이블이 필요하다 (N : 1로 나누어야 함)
📌 API 시큐리티 설정
.authorizeHttpRequests(authorizationRequest ->
authorizationRequest
.requestMatchers(
AntPathRequestMatcher.antMatcher("/"),
AntPathRequestMatcher.antMatcher("/user/**"),
AntPathRequestMatcher.antMatcher("/image/**"),
AntPathRequestMatcher.antMatcher("/subscribe/**"),
AntPathRequestMatcher.antMatcher("/comment/**"),
AntPathRequestMatcher.antMatcher("/api/**")
).authenticated()
.anyRequest().permitAll()
)
- "/api/**" 을 추가해줌
📌 Subscribe 엔티티 구현
package yerong.InstagramCloneCoding.domain.subs;
import jakarta.persistence.*;
import lombok.*;
import yerong.InstagramCloneCoding.domain.BaseTimeEntity;
import yerong.InstagramCloneCoding.domain.user.User;
@Builder
@Getter
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@Table(
uniqueConstraints = {
@UniqueConstraint(
name = "subscribe_uk",
columnNames = {"from_user_id", "to_user_id"}
)
}
)
public class Subscribe extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "subscribe_id")
private Long id;
@ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "from_user_id")
private User fromUser; //구독하는 유저
@ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "to_user_id")
private User toUser; //구독 받는 유저
}
- @Table(unique.. 이 코드는 두 User를 중복해서 얻는 일는 없도록 하기 위함이다
- ex) 1번 유저가 2번 유저를 팔로우했는데, 또, 1번이 2번을 팔로우하는 것을 방지
📌 Subscribe 리포지토리 구현
package yerong.InstagramCloneCoding.repository.subs;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import yerong.InstagramCloneCoding.domain.subs.Subscribe;
import java.util.Optional;
@Repository
public interface SubscribeRepository extends JpaRepository<Subscribe, Long> {
@Modifying
@Query(value = "INSERT INTO subscribe(from_user_id, to_user_id, createdAt) VALUES(:fromUserId, :toUserId, now())", nativeQuery = true)
int mSubscribe(Long fromUserId, Long toUserId);
@Modifying
@Query(value = "DELETE FROM subscribe WHERE from_user_id = :fromUserId and to_user_id = :toUserId", nativeQuery = true)
int mUnSubscribe(Long fromUserId, Long toUserId);
}
- UserId 값으로 받아왔지만, Subscribe를 만들기엔 User 오브젝트가 필요하기 때문에 네이티브 쿼리를 사용하는 것 추천(@Query)
- insert, delete, update 등 DB가 변동되는 쿼리는 @Modifying도 추가해줌
- 성공하면 변경된 행의 개수만큼 리턴됨, 실패하면 -1, 아무것도 일어나지 않는다면 0이 리턴됨
- ex) 10번 insert 했다면 10return
- ex) 1번 user를 delete 했는데 1 번 user가 없었다면 실패가 아니라 아무 일도 일어나지 않은 것이기 때문에 0 반환
📌 Subscribe 서비스 구현
package yerong.InstagramCloneCoding.service.impl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import yerong.InstagramCloneCoding.domain.subs.Subscribe;
import yerong.InstagramCloneCoding.handler.exception.CustomApiException;
import yerong.InstagramCloneCoding.handler.exception.CustomValidationApiException;
import yerong.InstagramCloneCoding.repository.subs.SubscribeRepository;
import yerong.InstagramCloneCoding.service.SubscribeService;
@Service
@RequiredArgsConstructor
public class SubscribeServiceImpl implements SubscribeService {
private final SubscribeRepository subscribeRepository;
@Override
@Transactional
public void subscribe(Long fromUserId, Long toUSerId){
try {
subscribeRepository.mSubscribe(fromUserId, toUSerId);
}catch (Exception e){
throw new CustomApiException("이미 구독을 하였습니다.");
}
}
@Override
@Transactional
public void unSubscribe(Long fromUserId, Long toUserId){
subscribeRepository.mUnSubscribe(fromUserId, toUserId);
}
}
- 네이티브 쿼리 사용
- 2번을 구독하기 위해 넘겼는데 또 넘기게 된다면 에러가 발생해야 하므로 예외처리를 해줌
- 실제 서비스에선 그렇게 되지는 않지만, postman 등 다른 방법으로 그렇게 들어올 수 있기 때문에 최대한 예외처리
📌 Subscribe 예외 처리
⚡️ CustomApiException
package yerong.InstagramCloneCoding.handler.exception;
import java.util.Map;
public class CustomApiException extends RuntimeException{
public CustomApiException(String message){
super(message);
}
}
⚡️ ControllerExceptionHandler 추가(등록)
@ExceptionHandler(CustomApiException.class)
public ResponseEntity<CMRespDto<?>> apiException(CustomApiException e){
return new ResponseEntity<CMRespDto<?>>(new CMRespDto(-1, e.getMessage(), null), HttpStatus.BAD_REQUEST);
}
📌 Subscribe 컨트롤러 구현
package yerong.InstagramCloneCoding.web.api;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import yerong.InstagramCloneCoding.config.auth.PrincipalDetails;
import yerong.InstagramCloneCoding.service.SubscribeService;
import yerong.InstagramCloneCoding.web.dto.CMRespDto;
@RestController
@RequiredArgsConstructor
public class SubscribeApiController {
private final SubscribeService subscribeService;
@PostMapping("/api/subscribe/{toUserId}")
public ResponseEntity<CMRespDto<?>> subscribe(
@PathVariable("toUserId") Long toUserId,
@AuthenticationPrincipal PrincipalDetails principalDetails){
subscribeService.subscribe(principalDetails.getUser().getId(), toUserId);
return new ResponseEntity<CMRespDto<?>>(new CMRespDto<>(1, "구독 성공", null), HttpStatus.OK);
}
@DeleteMapping("/api/subscribe/{toUserId}")
public ResponseEntity<CMRespDto<?>> unSubscribe(@PathVariable("toUserId") Long toUserId,
@AuthenticationPrincipal PrincipalDetails principalDetails){
subscribeService.unSubscribe(principalDetails.getUser().getId(), toUserId);
return new ResponseEntity<CMRespDto<?>>(new CMRespDto<>(1, "구독 취소 성공", null), HttpStatus.OK);
}
}
PREV
NEXT
728x90
반응형
'프로젝트 > 인스타그램 클론 코딩' 카테고리의 다른 글
[인스타그램 클론 코딩][Spring boot] 8. 구독 정보 뷰 렌더링 (0) | 2024.02.21 |
---|---|
[인스타그램 클론 코딩][Spring boot] 7. 프로필 페이지 (2) | 2024.02.20 |
[인스타그램 클론 코딩][Spring boot] 5. 회원 정보 수정 (1) | 2024.02.19 |
[인스타그램 클론 코딩][Spring boot] 4. 로그인 구현 (1) | 2024.02.18 |
[인스타그램 클론 코딩][Spring boot] 3. 회원 가입 구현 (1) | 2024.02.17 |