JPA를 활용하여 연관관계를 맺어줘서 게시글 하나만 삭제해도 관련된 댓글이 다 삭제가 되고, 게시글 하나만 불러도 연관된 댓글이 몽땅 다 삭제되는 것을 구현하는 것에 성공했습니다. JPA는 테이블을 객체처럼 사용한다에서 흰트를 얻어서 객체처럼 넣어주고 값을 불러주고 하는 방법으로 구현했습니다. 복잡한 코드를 작성하지 않더라도 전체 게시글 조회할 때 각 게시글에 연관된 댓글들이 함께 조회가 됩니다. 어제는 중첩 for문을 돌려서 전체 게시글(+댓글) 조회를 했는데 JPA로 for문 필요 없이 간단하게 불러오게 했습니다.

am....I ..... genius? no... jjajipgi dal-in

 

JPA 연관관계 사용 게시판, 댓글 구현하기

 

Post

package com.example.post.entity;

import com.example.post.dto.PostRequestDto;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;

@Getter
@Entity
@NoArgsConstructor
public class Post extends Timestamped{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String title;

    @Column(nullable = false)
    private String content;

    @Column(nullable = false)
    private String username;
//
    @ManyToOne(fetch = FetchType.LAZY)
    private User user;


// mappedBy로 연관관계 주인 설정, ManytoOne 다대일 관계설, "comment"가 외래키를 관리
    @OneToMany(mappedBy = "post", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
    private List<Comment> commentLists = new ArrayList<>();

    public Post(PostRequestDto requestDto, User user){
        this.content = requestDto.getContent();
        this.title = requestDto.getTitle();
        this.username = user.getUsername();
        this.user = user;

    }

    public void update(PostRequestDto requestDto) {
        this.content = requestDto.getContent();
        this.title = requestDto.getTitle();
    }
}
  • @ManyToOne으로 User 와의 관계를 설정해주었습니다. post를 작성한 user의 정보를 받아 올 수 있습니다. user 생성자를 작성했습니다.
  • @OneToMany로 Comment와의 관계를 설정해주었습니다. post에 작성한 댓글 목록을 불러올 수 있습니다.
  • 게시글을 삭제하면 연관된 댓글이 모두 삭제가 되게 cascade = CascadeType.REMOVE 를 붙여주었습니다.

 

Comment

package com.example.post.entity;

import com.example.post.dto.CommentRequestDto;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@Entity
public class Comment extends Timestamped{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String username;

    @Column(nullable = false)
    private String comment;

    //여러 댓글을 한 게시글에 작성
    @ManyToOne(fetch =FetchType.LAZY)
    @JoinColumn(name = "post_id")
    private Post post;

    //여러 댓글을 한명의 user가 작성
    @ManyToOne(fetch =FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    public Comment(String comment, User user, Post post){
        this.username = user.getUsername();
        this.user = user;
        this.comment = comment;
        this.post = post;


    }

    public void update(CommentRequestDto commentRequestDto){
        this.comment = commentRequestDto.getComment();
    }
}
  • @ManytoOne 어노테이션을 넣어서 Post와의 관계를 설정해주었습니다. 한 post에 여러 댓글을 작성할 수 있습니다.
  • @ManytoOne 어노테이션을 넣어서 User와의 관계를 설정해주었습니다. 한명의 user가 여러 댓글을 작성할 수 있습니다.
  • Comment 생성자에 User와 Post를 넣어서 객체로 불러 올 수 있게 작성하였습니다.

 

User

package com.example.post.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;

@Getter
@NoArgsConstructor
@Entity(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String username;

    @Column(nullable = false)
    private String password;

    @Column(nullable = false)
    @Enumerated(value = EnumType.STRING)
    private UserRoleEnum role;

    @OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
    private List<Post> posts = new ArrayList<>();

    @OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
    private List<Comment> comments = new ArrayList<>();


    public User(String username, String password, UserRoleEnum role) {
        this.username = username;
        this.password = password;
        this.role = role;
    }
}
  • @OneToMany로 Comment 및 Post의 관계를 설정해주었습니다. 
  • user를 삭제하면 연관된 댓글 및 게시글이 모두 삭제가 되게 cascade = CascadeType.REMOVE 를 붙여주었습니다.

 

PostResponseDto

package com.example.post.dto;

import com.example.post.entity.Post;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class PostResponseDto {

    private Long id;
    private String title;
    private String content;
    private String username;
    private LocalDateTime createdAt;
    private LocalDateTime modifiedAt;

    private List<CommentResponseDto> commentResponseDtoList = new ArrayList<>();

    //stackoverflow를 방지하기 위해서 객체 직렬화를 해야한다. stream을 써야하나보다?
    public PostResponseDto(Post post){
        this.id = post.getId();
        this.title = post.getTitle();
        this.content = post.getContent();
        this.username = post.getUser().getUsername();
        this.createdAt = post.getCreatedAt();
        this.modifiedAt = post.getModifiedAt();
        this.commentResponseDtoList = post.getCommentLists().stream().map(CommentResponseDto::new).collect(Collectors.toList());

    }
}

 

  • 생성자에 getUser().getUsername()을 사용해서 username을 불러왔습니다.
  • 댓글리스트는 Dto에 담아서 불러옵니다. 바로 가져오면 stackoverflow, 순환 참조 오류가 발생합니다.
  • 이를 해결하기 위해 구글링해보니 dto에 담아오면 되고 목록을 담으려면 stream을 쓰면 된다고 해서 썼는데 여기는 정말 그냥 가져온거라서 나중에 stream에 대해서 학습하고 더 나은 방법이 있나 알아보도록 하겠습니다.

 

CommentResponseDto

package com.example.post.dto;

import com.example.post.entity.Comment;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Getter
@NoArgsConstructor
public class CommentResponseDto {

    private Long id;
    private String username;
    private String comment;
    private LocalDateTime createdAt;
    private LocalDateTime modifiedAt;

    private Long postId;

    private Long userId;

    public CommentResponseDto(Comment comment) {
        this.id = comment.getId();
        this.username = comment.getUsername();
        this.comment = comment.getComment();
        this.createdAt = comment.getCreatedAt();
        this.modifiedAt = comment.getModifiedAt();
        this.postId = comment.getPost().getId();
        this.userId = comment.getUser().getId();
    }
}
  • postId는 comment.getPost().getId()를 사용해서 불러왔습니다. 
  • userId는 comment.getUser().getId()를 사용해서 불러왔습니다.
  • JPA 연관관계를 설정했기 때문에 메소드로 호출할 수 있습니다.

 

PostService

  //게시글 전체 조회 USER/ADMIN 상관 없음
    public List<PostResponseDto> getPostList() {
        List<Post> postList = postRepository.findAllByOrderByModifiedAtDesc();

        if(postList.isEmpty()){
            throw new NullPointerException("게시글이 존재하지 않습니다.");
        }

        List<PostResponseDto> postResponseDtoList = new ArrayList<>();
        //각 게시물마다 조회해서 넣어줌
        for (Post post : postList) {
            postResponseDtoList.add(new PostResponseDto(post));
        }
        return postResponseDtoList;
    }

    @Transactional(readOnly = true)
    //게시글 상세 조회 USER/ADMIN 상관 없음
    public PostResponseDto getPost(Long id) {
        Post post = postRepository.findById(id).orElseThrow(
                () -> new NullPointerException("해당 게시글은 존재하지 않습니다.")
        );

//        List<CommentResponseDto> commentResponseDtoList = new ArrayList<>();

//        for (int i =0; i<post.getCommentLists().size(); i++){
//            Comment comment = post.getCommentLists().get(i);
//            commentResponseDtoList.add(new CommentResponseDto(comment));
//        }
        return new PostResponseDto(post);

    }
  • 게시글을 불러오면 댓글은 자동으로 따라오기 때문에 댓글 관련해서 작성했던 코드를 지워주었습니다. 
  • JPA 연관관계 설정을 잘 해놓으니 매우 편리하네요!!

 

 

결론

JPA 연관관계를 도대체 어떻게, 왜 쓰는지를 해결했습니다. 스프링 첫주차부터 JPA 연관관계 강의를 들었는데 이제사 좀 이해가 되는 것 같습니다. 연관관계를 설정해 놓으면 알아서 데이터가 연관되어서 불러오고 삭제하는 것이 간단해집니다. 연관관계 설정도 조금만 더 연습해보면 잘 할 수 있을 것 같습니다. 시간이 괜찮으면 좋아요와 대댓글 기능도 구현을 해보려 합니다.

 

내일부터는 프론트와 함께 하는 첫 협업 프로젝트날입니다😎

Spring과 Java는 사랑입니다😍😍

+ Recent posts