JPA를 활용하여 연관관계를 맺어줘서 게시글 하나만 삭제해도 관련된 댓글이 다 삭제가 되고, 게시글 하나만 불러도 연관된 댓글이 몽땅 다 삭제되는 것을 구현하는 것에 성공했습니다. JPA는 테이블을 객체처럼 사용한다에서 흰트를 얻어서 객체처럼 넣어주고 값을 불러주고 하는 방법으로 구현했습니다. 복잡한 코드를 작성하지 않더라도 전체 게시글 조회할 때 각 게시글에 연관된 댓글들이 함께 조회가 됩니다. 어제는 중첩 for문을 돌려서 전체 게시글(+댓글) 조회를 했는데 JPA로 for문 필요 없이 간단하게 불러오게 했습니다.
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는 사랑입니다😍😍
'TIL' 카테고리의 다른 글
TIL 좋아요는 사랑입니다❤️ 221217 (0) | 2022.12.17 |
---|---|
TIL 첫 포트폴리오 프로젝트 시작 221216 (0) | 2022.12.17 |
TIL CRUD 마스터의 길 3 게시판 댓글 기능 구현 221214 (0) | 2022.12.14 |
TIL CRUD 마스터의 길 2 221213 (0) | 2022.12.14 |
TIL CRUD 마스터의 길 221212 (0) | 2022.12.13 |