[뉴스피드 프로젝트] 프로필 조회하기 / JPA 활용

말하는 감자·2025년 4월 8일

내일배움캠프

목록 보기
37/73

지난시간에 양방향 연관관계를 없애면서
레포지토리에 직접 쿼리문을 넣을 수 잇는 아주 좋은 기능들을 발견했다.
그래서 오늘? 이번? 기회에 적극 활용해서 까먹지 않도록 노력해보겠다..

이전 포스트 : https://velog.io/@tofha054/JPA-양방향-연관관계-없애기

목표

유저 아이디 값에 맞는 프로필 정보 반환하기

계획

주석으로 프로필을 파악하는 함수가 어떤 순서대로 움직여야할지 정리했다.
다 만들고나면 저 내용들 정리해서 위에 자바독 주석으로 쓸 예정

@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService{

    //유저 레포지토리
    UserRepository userRepository;
    //팔로우 레포지토리
    FollowingRepository followingRepository;

    @Override
    public UserResponseDto getProfile(Long loginUserId, Long UserId) {

        //로그인 한 유저가 자신의 프로필에 들어왔을 경우
        if(loginUserId.equals(UserId)){
            //팔로우, 팔로잉 상태 보여주지 않는다.
            // 이름, 상태메시지, 팔로우/팔로워 수, 게시글 목록

        }else{ // 다른 사람의 프로필에 들어갔을 경우

            //해당 사용자의 프로필이 퍼블릭 상태인지 확인
            //팔로우상태인지 확인

            // 1. 언팔로우 + 프라이빗 계정
            // 볼 수 없다 메시지  + ( 200 ok ) 리턴

            // 2. 그 외( 언팔로우 + 퍼블릭 / 팔로우 + 퍼블릭,프라이빗 )
            //해당 사용자의 이름, 상태메시지, 팔로우 여부, 팔로우/팔로워 수, 게시글 목록
        }
        return null;
    }
}

여기서 로그인 여부는 컨트롤러에서 파악이 될 것이고,
이번 프로젝트에서는 세션이 아니라 JWT를 사용하기 때문에 로그인 정보에 대해서 어떻게 받아올지도 고민해야 한다.

JWT에서 로그인 한 유저의 아이디만 저장하도록 하기로 했으니
컨트롤러에서 아이디값만 get으로 받아와서 넣어주면되겠다!






반환하는 Dto

보면 아이디, 이름, 소개, 공개/비공개여부, 팔로잉과 팔로워 수 자체는
남의 것을 보던 내 것을 보던 공통적으로 들어가기때문에 final로 생성자에서 주입하도록 해주었고,
남의 것을 볼 때만 생기는 팔로잉 여부는 세터값으로 지정해주었다.

팔로잉/팔로워

public interface FollowingRepository extends JpaRepository<Following, Long> {

    //팔로잉 수 반환
    @Query("SELECT COUNT(*) FROM Following WHERE user = :userId")
    Long countByFollowingUserByUserId(@Param("userId") Long userId);

    //팔로워 수 반환
    @Query("SELECT COUNT(*) FROM Following WHERE followingUser = :userId")
    Long countByFollowingUserByFollowingUserId(@Param("userId") Long userId);

    boolean existsByFollowingUserIdAndUserId(Long userId, Long followingUserId);
}

함수이름이 길긴한데 완전 알아보기쉬운듯

existsByFollowingUserIdAndUserId은 로그인한 사용자가 접근한 유저를 팔로우 했는지 확인하는 함수다
JPA에서 이름토대로 함수만들어주다보니 저 함수를 SQL그대로 풀어보면

SELECT CASE WHEN COUNT(*) > 0 THEN TRUE ELSE FALSE END
FROM following
WHERE following_user_id = ? AND user_id = ?;

이런식으로 나오지않을까????

이렇게되면 countByFollowingUserByUserIdcountByFollowingUserByFollowingUserId도 위에 JPQL안써도될거같음.

    //팔로잉 수 반환
    Long countByFollowingUserByUser_id(Long userId);

    //팔로워 수 반환
    Long countByFollowingUserByFollowingUser_id(Long followingUserId);

    boolean existsByFollowingUserIdAndUserId(Long userId, Long followingUserId);

짧게 바꿔줬다.

💡 JPA 함수 명명 규칙

원래 함수 이름을 countByFollowingUserByUserId 이렇게 짰는데 JPQL형식이 아니라서 오류가 났다.
그대신 countByFollowingUserByUser_id 이라는 함수는 바로 적용이 된다.
왜 이럴까??

작동 원리 : _를 사용한 필드 접근

  • JPA에서 참고해야할 객체 이름이나 컬럼은 객체->필드 순서로 접근하게 된다.
  • 지금 By 뒤에 보면 User_id인데 풀어서 User객체 안에 있는 id를 접근해라. 가 되는 것이다.
  • JPA는 엔티티를 바탕으로 데이터베이스 컬럼 접근을 하니, 특정 엔티티의 필드와 매핑을 하게끔 만들어 줘야 한다.






프로필 주인이 쓴 게시글

여기서도 제약조건이 붙는다
게시글에 is_public이라는 컬럼이있는데
이 컬럼이 false가되면 작성한 본인만 게시글을 확인할 수 있다.
그래서 자신의 프로필을 열었으면 제약 없이 모든 게시글을 불러와야 하고

남의 게시글일 경우 is_public = true인 게시글만 불러온다.

    @Query(value = "SELECT Post FROM Post WHERE user = :user_id AND isPublic = true"
        + " ORDER BY updatedAt DESC ")
    List<Post> getAllPublicPostsByUser_id(Long userId);

    List<Post> getAllByUser_id(Long userId);

해당 조건을 충족시키는 데이터들을 반환하는 메서드들이다
위는 sql문 보면 알겠지만 퍼블릭한 게시글만 불러오는 것이다.
아래는 그냥 다불러오는거고(자신이 작성한 글)

soft Delete 조건

을 빼먹었다.
게시글이나 회원같은 경우는 삭제한다고 바로 delete sql문 쏴버리는게아니라 deleteAt에 날짜 기록한 후에 천천히 삭제해야하니깐
그것을 감안해야한다...
삭제가 안되었으면 deletedAt이 null상태니깐
null인 row라는 기본조건이 생김

    //공개 게시글 + 삭제되지 않은 게시글 + 수정일 기준 최신순 정렬
    @Query(value = "SELECT Post FROM Post "
        + "WHERE user = :user_id "
        + "AND isPublic = TRUE "
        + "AND deletedAt IS NULL "
        + "ORDER BY updatedAt DESC ")
    List<Post> getAllPublicPostsByUser_id(Long userId);

    List<Post> getAllByUser_IdAndDeletedAtIsNullOrderByUpdatedAt(Long userId);

    // 삭제되지 않은 게시글 + 수정일 기준 최신 정렬 ( 함수이름 가독성이 좋지않아서 따로 함더감쌌음)
    default List<PostResponseDto> getAllMyPosts(Long userId){
        return getAllByUser_IdAndDeletedAtIsNullOrderByUpdatedAt(userId)
            .stream().map(PostResponseDto::new).collect(Collectors.toList());
    }

으워하나같이 다 쿼리문이 길어졌다.
위에껀 사실 더 길게만들면 JPQL쓸일이 없긴한데
그럼또 함더 감싸줘야할 것 같아서 그냥 그대로 놔두었다. ㅎ;

1차 완성

그렇게 1차적으로 완성된
프로필 조회하기 함수

/**
     * [Service] 프로필 조회 함수
     * 1. controller에서 받아온 유저값 검증
     * 2. 다른사람의 프로필 + 팔로우 안했음 + 상대방이 프로필 비공개 상태 -> 403
     * 3. 자신의 프로필 조회일 경우
     *      - 비공개 개시글 표시o, 팔로잉 여부 표시x
     * 4. 타인의 프로필 조회일 경우
     *      - 비공개 게시글 표시x, 팔로잉 여부 표시o
     * @param loginUserId 현재 로그인 중인 유저 아이디
     * @param UserId 프로필 조회할 유저 아이디
     * @return UserProfileResponseDto 프로필 조회 내용
     *      - 해당 사용자의 이름, 상태메시지, 팔로우 여부, 팔로우/팔로워 수, 게시글 목록
     * @throws 403 해당 페이지 접근 권한이 없기 때문에 예외 발생
     */
    @Transactional(readOnly = true)
    @Override
    public UserProfileResponseDto getProfile(Long loginUserId, Long UserId) {
        User user = userRepository.findByUserIdOrElseThrow(UserId);

        if(!loginUserId.equals(UserId)
            && followingRepository.existsByFollowingUserIdAndUserId(loginUserId, UserId)
            && !user.isPublic()){
            throw new ResponseStatusException(HttpStatus.FORBIDDEN,
                "이 사용자는 프로필 비공개 설정 상태이며, 친구가 아닌 경우 정보 열람이 제한됩니다.");
        }

        //팔로잉, 팔로워 수 구하기
        Long followingCnt = followingRepository.countByUser_id(UserId);
        Long followerCnt = followingRepository.countByFollowingUser_id(UserId);

        if(loginUserId.equals(UserId)){
            //게시글 가져오기 - 자기자신의 프로필이라 isn't public 한 게시글도 다 불러옴
            List<PostResponseDto> posts = postRepository.getAllMyPosts(UserId);

            return new UserProfileResponseDto(
                user.getId(),
                user.getName(),
                user.getIntroduction(),
                user.isPublic(),
                followingCnt.intValue(),
                followerCnt.intValue(),
                posts
            );
        }else{
            List<Post> posts = postRepository.getAllPublicPostsByUser_id(UserId);

            return new UserProfileResponseDto(
                user.getId(),
                user.getName(),
                user.getIntroduction(),
                user.isPublic(),
                followingCnt.intValue(),
                followerCnt.intValue(),
                followingRepository.existsByFollowingUserIdAndUserId(loginUserId, UserId),
                posts.stream().map(PostResponseDto::new).collect(Collectors.toList())
            );
        }
    }
profile
대충 데굴데굴 굴러가는 개발?자

0개의 댓글