본문 바로가기
스파르타(부트캠프)/Troubleshooting

[내일배움캠프] 뉴스피드 프로젝트에 대한 트러블슈팅

by Kimuky 2024. 11. 22.

1.  외래키 설정 (Friend Table)

 

1) 개요

- 유저 테이블에서 외래키를 통해 친구 테이블을 구성할려고 한다.

- 그럼 여러가지 방법이 있을 것이다.

 

a. friend( id[pk], to_user_id[fk] , from_user_id[fk] )

- 처음에는 이렇게 구현할려고 했다. 하지만 굳이 자체 아이디가 PK를 가져야 하는 의문이 생겼다. 구현하는에 있어서는 큰 문제가 없다.

 

b. friend( from_user_id (pk) , to_user_id(fk) )

- 두번째로 생각한 것은 from_user_id에 대한 것은 로그인 정보를 통해 들고 올 수도 있기도 하고 굳이 유저와 연관관계를 맺어야 하는지 의문이 들었다. 물론 구현하는데에는 큰 문제가 없다.

 

c. friend( from_user_id (pk, fk) , to_user_id(pk, fk) )

- 어떻게 보면 가장 이상적인 상황이라 생각했다. 물론 이것을 Java Spring Entity로 정의를 어떻게할지 감이 잡히지 않았다.

이것도 구현하는데 큰 문제가 없다.

 

 

그래서 a방법은 PK 값이 불필요있다고 생각하고, c 방법은 어떻게 구현하는지 몰라서 b 방법으로 일단 진행하였다.

 

잘 만들어졌다. 이제 이것을 통해 구현을 해보자

 

2) 문제 상황

- 친구 요청, 친구 수락에 대해서는 잘 구현을 할 수 있지만, 로그인한 사용자에게 친구 요청한 사용자의 리스트를 뽑는것에 대해 문제가 발생

 

무엇이 문제?

 

FriendRepository.java

//friendRepository
public interface FriendRepository extends JpaRepository<Friend, Long> {
    List<Friend> findFriendByToUserId(User save2);

}

 

Jpa메소드를 통해 도저히 from_user_id와 user를 join을 어떻게 시키는지 모르겠다.

 

그러면 여기서 일단은 생각해본 것은 Jpa 메소드 말고 Query를 통해 데이터베이스에서 데이터를 불러보고자 한다.

 

여기서 방법은

1. JPA메소드

2. JPQL

3. nativeQuery가 있었다.

 

일단은 가장 쉬운 방법이라고 생각한 3번 방법을 통해 쿼리를 날려보았다.

- nativeQuery

public interface FriendRepository extends JpaRepository<Friend, Long> {
    @Query(
            value = "SELECT  u.id, u.name FROM friend f join user u 
            on f.from_user_id = u.id where f.user_id = :user_id", nativeQuery = true
    )
    List<Object[]> fd(@Param(value="user_id") Long user_id);
}

 

디버깅을 통해 값을 분석

 

잘 전달 되는 것을 확인 할 수 있다? 근데 형태가 조금 이상하다. id, name key가 없다. value만 온 것이다.

물론 처리를 할 수 있다.

   @Transactional
    public List<FriendResponseDto> findAllByEmail(String email) {
        List<Object[]> byUser = friendRepository.fd(findUser.getId());
        return byUser.stream().map(result 
        -> new FriendResponseDto((Long)result[0], (String)result[1])).toList();
    }
}

 

하지만 이렇게 처리하는게 맞지 않는 것 같았다. 처음부터 dto로 받아올 수 없을까 해서 이제는 Jpql을 통해 해볼려고 한다.

 

- JPQL

1시간 정도 고민해보고 디버깅해보았는데 자꾸 컨버터 오류가 나서 튜터님께 찾아갔다.

public interface FriendRepository extends JpaRepository<Friend, Long> {

    @Query(value = "select new com.example.newsfeedproject.dto.friend.
    FriendListResponseDto(u.id, u.name) from friend f 
    join f.fromUserId u where f.toUserId = :user_id")
    List<FriendListResponseDto> findFriendList(@Param(value = "user_id") User user_id);
}

이렇게하니 해결이 된다.  new com.example 저런 패키지 구조까지 다 보여지는게 이상했다.

 

그래서 또 고민을 해보았다. JPA메소드를 통해 편하게 가져올 순 없을까 분명히 연관관계를 맺었는데 가져오는게 너무 불편하다.

 

3) 해결

- JPA 메소드

JPA메소드로 충분히 가능하다. 하지만 Entity 관계를 맺어주어야만 JPA메소드를 활용할 수 있다.

c 방법으로 테이블을 구성해야한다.

또한, 복합키를 사용할 예정이기에 FriendId 라는 Entity도 만들어주어야한다.

필요한 것

- @Embeddable

- @EmbeddedId

- @MapsId

//FriendId.java
@Embeddable
public class FriendId implements Serializable {

    private Long fromUserId;

    private Long toUserId;

    public FriendId(Long id, Long id1) {
        this.fromUserId = id;
        this.toUserId = id1;
    }

    public FriendId() {
    }
}
// Friend.java

@Getter
@Entity(name = "friend")
public class Friend {

    @EmbeddedId
    private FriendId id;

    @MapsId("fromUserId")
    @ManyToOne
    @JoinColumn(name = "from_user_id")
    private User fromUserId;

    @MapsId("toUserId")
    @ManyToOne
    @JoinColumn(name = "to_user_id")
    private User toUserId;

    @Column(columnDefinition = "bit")
    private int friendRequest;

    private LocalDateTime relatedAt;

    public Friend(User fromUserId, User toUserId) {
        this.id = new FriendId(fromUserId.getId(), toUserId.getId());
        this.fromUserId = fromUserId;
        this.toUserId = toUserId;
        this.friendRequest = 0;
    }

    public Friend(User fromUserId, User toUserId, int friendRequest) {
        this.id = new FriendId(fromUserId.getId(), toUserId.getId());
        this.fromUserId = fromUserId;
        this.toUserId = toUserId;
        this.friendRequest = friendRequest;
        this.relatedAt = LocalDateTime.now();
    }

    public Friend() {
    }
}
public interface FriendRepository extends JpaRepository<Friend, FriendId> {

    // 친구 상태코드에 따른 검색
    List<Friend> findFriendByFriendRequestAndToUserId(int i, User findUser);

}
    @Transactional
    public List<FriendListResponseDto> findFriendListByFriendRequest(String email, int friendRequest) {
        User findUser = findUserByEmailOrElseThrow(email);

        // friendRequest 와 로그인한 유저 값을 이용해 친구테이블에서 검색
        List<Friend> FriendList = friendRepository.
        findFriendByFriendRequestAndToUserId(friendRequest, findUser);

        // 가져오 친구리스트를 통해 아이디, 닉네임으로 변환 스트림
        List<FriendListResponseDto> changeResponseList = FriendList.stream().map(
                Friend -> {
                    User user = Friend.getFromUserId();
                    return new FriendListResponseDto(user.getId(), user.getName());
                }
        ).toList();

        return changeResponseList;
    }

 

4) 결론

- 엔티티에 대해서 제대로 이해하지 못했다. 그렇기에 연관관계의 이점에 대해서 이해를 하지 못했다. 쿼리로만 해결하려는 문제를 해결해야겠다.

- 대부분의 기능은 Jpa메소드를 통해 가능하기에 어려운 쿼리 아닌 이상 적극 활용하기

- Jpa 는 굉장히 편하다..

 

 

--- TBU