스프링부트 입문기 #3 - 테이블 JOIN

HanSH·2023년 7월 3일
0

SpringBoot 삽질기

목록 보기
5/12

전 게시물에서는 테이블을 하나만 만들어서 사용하였다.
이제는 실제 게시판처럼 유저가 작성한 모든 게시글을 출력해보도록 해보자!

※ JPA를 쓰고있다. MyBatis는 아직 뭔지도 몰?루

데이터베이스 수정/추가

먼저, post 테이블을 만들어보자.
패키지로 만드는 것이 관리하기 편하여 post를 담당하는 패키지, user를 담당하는 패키지 구조로 변경하였다.

기존에 작성한 Test% 이름의 파일들은 전부 User로 리팩토링 하였다.
사랑해요 IntelliJ!

// Post.java
@Entity
@Data
public class Post {
    @Id @GeneratedValue(strategy = GenerationType.AUTO)
    private Long Id;

    private String title;
    private String content;

    @ManyToOne
    private User user;
}

@ManyToOne 어노테이션을 통해 하나의 유저가 여러 개의 게시글을 작성할 수 있다고 알려준다.

기존의 User Entity에는 아래를 추가하자.

// User.java
    @OneToMany(mappedBy = "user", cascade = CascadeType.REMOVE)
    private List<Post> posts;

@OneToMany를 통해 하나의 유저에는 여러 게시글이 있다는 것을 알려준다.
이때 mappedBy를 통해 Post의 어떤 항목과 Join되어있는지 명시해준다.
cascade를 통해 해당 유저가 삭제되면 모든 게시글을 삭제한다.

컨트롤러, 서비스, 레포지터리 생성

이전 글에 작성한 내용을 참고한다.

// PostRepository.java
public interface PostRepository extends JpaRepository<Post, Long> {
}
// PostService.java
@RequiredArgsConstructor
@Service
public class PostService {
    private final PostRepository postRepository;

    public Post push(Post post) {
        return postRepository.save(post);
    }
    
    public Post getPost(Long id) {
        Optional<Post> postOptional = postRepository.findById(id);
        if (postOptional.isPresent()) {
            return postOptional.get();
        }
        return null;
    }
}
// PostController.java
@RequiredArgsConstructor
@Controller
@RequestMapping("/posts")
public class PostController {
    private final UserService userService;
    private final PostService postService;

    @GetMapping("/{id}")
    public Post getPost(@PathVariable Long id) {
        Post post = postService.getPost(id);
        return post;
    }

    @PostMapping("")
    public Post postPost(@RequestBody PostRequest req, @RequestHeader String token) {
        User user = userService.findByName(token);
        Post post = new Post();
        post.setUser(user);
        post.setTitle(req.getTitle());
        post.setContent(req.getContent());
        return postService.push(post);
    }
}

원래는 위와 같이 짜면 안되지만, 간단한 테스트를 위해 위와 같이 작성하였다.

기존 UserController 수정

// UserController.java
    @GetMapping("/{id}")
    public ResponseEntity<UserResponse> getTest(@PathVariable("id") Long id) {
        User t = userService.findById(id);
        UserResponse res = new UserResponse();
        res.setName(t.getName());
        res.setPosts(t.getPosts());
        return ResponseEntity.status(HttpStatus.OK).body(res);
    }

유저 id를 받아와 해당 유저가 작성한 모든 게시글을 return해 줄 것으로 추측된다.

데이터를 다음과 같이 넣었다.

이제 /users로 모든 유저의 데이터를 가져와보자.

어라... 무한 참조가 발생하였다.
대체 왜일까...

검색해보니 JPA는 양방향 Entity일때 기본적으로 엔티티를 전부 참조한다고 한다. 그러니 무한 참조가 발생할수밖에...
DTO를 만들어 반환하도록 하였다. 추가될 게시판 -> 게시글 -> 댓글 형식만 봐도, DTO를 만드는 것이 좋다고 생각하였다.
다른 방법은 이곳으로.

DTO 생성

// PostResponse.java
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class PostResponse {
    private Long id;
    private String title;
    private String content;
}
// UserResponse.java
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class UserResponse {
    private String name;
    private List<Post> posts;
}

DTO(Data Access Object)를 위와 같이 생성한다.

UserController 수정

// UserController.java
@Controller
@RequiredArgsConstructor
@RestController
@RequestMapping("/users")
public class UserController {
    private final UserService userService;

    @GetMapping("")
    public ResponseEntity<List<UserResponse>> getAllUsers() {
        List<UserResponse> res = getUserDetails(userService.getAll());
        return ResponseEntity.status(HttpStatus.OK).body(res);
    }

    @GetMapping("/{id}")
    public ResponseEntity<UserResponse> getUser(@PathVariable("id") Long id) {
        UserResponse res = getUserDetail(userService.findById(id));
        return ResponseEntity.status(HttpStatus.OK).body(res);
    }

    private UserResponse getUserDetail(User t) {
        UserResponse res = new UserResponse();
        res.setName(t.getName());
        res.setPosts(getUserWritingDetail(t.getPosts()));
        return res;
    }
    
    private List<UserResponse> getUserDetails(List<User> t) {
        List<UserResponse> res = new ArrayList<>();
        t.forEach(a -> res.add(new UserResponse(a.getName(), getUserWritingDetail(a.getPosts()))));
        return res;
    }

    private List<PostResponse> getUserWritingDetail(List<Post> p) {
        List<PostResponse> res = new ArrayList<>();
        p.forEach(a -> res.add(new PostResponse(a.getId(), a.getTitle(), a.getContent())));
        return res;
    }
}

새로 추가된 함수를 알아보자.

  • private UserResponse getUserDetail(User t);
    UserResponse DTO를 이용하여 반환값을 설정한다.

  • private List<PostResponse> getUserWritingDetail(List<Post> p);
    모든 유저의 결과를 반환한다. 실제 서비스에서는 사용하지 않는다.

  • private List<UserResponse> getUserDetails(List<User> t);
    유저가 작성한 글 목록을 받아와 PostResponse DTO List 형식으로 반환한다.

자, 이제 get 요청을 보내보자!

정상적으로 전부 받아와지는 것을 볼 수 있다!

velog 쓰기 전, 이 부분에서 많이 헤맸는데 Entity간 참조 문제였다...

축하한다! 이게 간단한 Join을 실행할 수 있다!

profile
저는 말하는 싹 난 감자입니다

0개의 댓글