Super Kawaii Cute Cat Kaoani [인스타그램 클론 코딩][Spring boot] 6. 구독하기

[인스타그램 클론 코딩][Spring boot] 6. 구독하기

2024. 2. 19. 21:00
728x90
SMALL

✅ 연관관계

  • 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 등 다른 방법으로 그렇게 들어올 수 있기 때문에 최대한 예외처리
728x90

📌 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

 

[인스타그램 클론 코딩] 5. 회원 정보 수정

📌 회원 정보 수정 항목 name // 필수 값 password //필수 값 website bio phone gender ⚡️ UserController 세션에서 user를 가져와서 model로 보낸다. @GetMapping("/user/{id}/update") public String update(@PathVariable("id") Long id

nyeroni.tistory.com

NEXT

 

[인스타그램 클론 코딩][Spring boot] 7. 프로필 페이지

포토 이미지 등록 multipart/form-data UUID 포토 이미지 렌더링 엔티티에 파일이 아닌 url 저장 사진을 전송받으면 그 사진을 서버에 특정 폴더에 저장하게 될 것임(DB에 그 경로를 insert 할 것!) 📌 Image

nyeroni.tistory.com

 

 

 

SMALL

 

728x90
LIST

BELATED ARTICLES

more