Entity와 DTO, 게시글과 대댓글을 구현할 수 있는 DB
Entity와 DTO의 차이, 언제 무엇을 써야하는지에 대하여 정리해보겠다.
먼저, 기본적인 특징에 대해서 알아보자.
Entity의 특징
영속성(Persistence)을 가짐: Entity는 일반적으로 데이터베이스의 테이블에 영속적으로 저장되는 데이터를 나타낸다. 이는 Entity가 데이터의 영구적인 저장과 조회를 위해 설계되었음을 의미한다.
식별자를 가짐: 대부분의 Entity는 고유 식별자(ID)를 가지며, 이는 데이터베이스의 기본 키(Primary Key)에 해당한다. 이 식별자를 통해 특정 Entity 인스턴스를 구별하고 참조할 수 있다.
비즈니스 로직을 포함할 수 있음: Entity는 단순히 데이터를 저장하는 것 이상의 역할을 할 수 있다. 비즈니스 로직을 메소드로 구현하여 Entity 내에 포함시킬 수 있으며, 이를 통해 데이터의 일관성과 무결성을 유지할 수 있다.
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@Entity
@Table(name = "user_table")
public class UserEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable=false)
private String name;
@Column(nullable=false)
private String birthdate;
위는 Entity클래스의 예시이다. 이는 데이터베이스의 table을 나타낸다
DTO의 특징
계층간 데이터 전송에 최적화됨: DTO는 다른 계층 간의 데이터 교환을 위해 최적화된 객체이다. 이는 데이터의 전송을 위한 컨테이너 역할을 하며, 필요한 데이터만을 포함하여 전달할 수 있다.
로직을 포함하지 않음: DTO는 순수하게 데이터 전송을 목적으로 하기 때문에, 어떠한 비즈니스 로직도 포함하지 않는다. DTO의 주요 목적은 계층 간 데이터 교환의 효율성을 높이는 것이다.
데이터 포맷의 유연성: DTO를 사용하면 클라이언트에 전달되는 데이터의 구조를 Entity와 독립적으로 설계할 수 있다. 이를 통해 애플리케이션의 내부 구조 변경 없이도 클라이언트가 요구하는 데이터 포맷을 제공할 수 있다.
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
public class UserDTO {
private Long id;
private String name;
private String birthdate;
위는 DTO클래스의 예시이다. 사용자의 이름과 생년월일만 전달하기 위해 사용된다.
예를 들어, 사용자 등록이나 정보 업데이트 시 사용자로부터 받은 데이터를 서버로 전달하는 데 사용할 수 있다.
차이점 요약
목적: Entity는 데이터베이스의 테이블을 나타내며, 데이터의 유지보수와 관리에 사용된다. DTO는 계층간 데이터 전달에 사용되며, 필요한 데이터만을 담아서 전달하는 역할을 한다.
로직 포함 여부: Entity는 비즈니스 로직을 포함할 수 있지만, DTO는 데이터 전달의 용도로만 사용되므로 로직을 포함하지 않는다.
사용되는 위치: Entity는 주로 데이터베이스와의 상호작용에 사용되며, DTO는 서비스 계층과 컨트롤러 계층 사이, 또는 클라이언트와 서버 사이의 데이터 전송에 사용된다.
이제, 게시판과 대댓글을 구현할 수 있는 댓글 DB를 만들어보자.
유튜브로 먼저 영상을 좀 보고 만들어보았다.
먼저 postEntity이다
@Entity
@Table(name = "post_table")
public class PostEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable=false)
private String postTitle;
@Column(nullable=false)
private String postContent;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name="userId")
private UserEntity userEntity;
@Column(nullable=false)
private String region;
@Column(nullable=false)
private String subregion;
@OneToMany(mappedBy = "postEntity", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
private List<ReplyEntity> replyEntity;
@CreationTimestamp
private Timestamp postCreatedTime;
}
여기서 다른 것들은 사용자 관련하여 다룰 때와 비슷하지만, 아래 두 가지가 좀 특이하다.
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name="userId")
private UserEntity userEntity;
@OneToMany(mappedBy = "postEntity", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
private List<ReplyEntity> replyEntity;
먼저 userEntity에서 @ManyToOne 은 일대다 형식을 나타낸다. 즉, 한 사람이 여러 개의 글을 작성할 수 있다.
또한, userId는 userEntity를 참조하는 외래키가 된다. 즉, 게시글 데이터베이스의 userId칸은 게시글을 작성한 사람의 고유 Id값으로 저장된다.
@OneToMany는 반대를 나타낸다. 하나의 글에 여러개의 댓글이 생성될 수 있다.
mappedBy = "postEntity"라는 것은 ReplyEntity 안에서 이 관계의 반대편을 나타내는 필드명을 지정한다. 즉, ReplyEntity 내에는 PostEntity를 참조하는 postEntity 가 있어야한다.
마지막으로 cascade=CascadeType.REMOVE를 통하여 postEntity가 삭제될 시 관련 replyEntity들이 같이 삭제되도록 설정하였다.
다음은 ReplyEntity이다
@Entity
@Table(name = "reply_table")
public class ReplyEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String replyContent;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "userId")
private UserEntity userEntity;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "postId")
private PostEntity postEntity;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "replyId")
private ReplyEntity replyEntity;
@OneToMany(mappedBy = "ReplyEntity", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
private List<ReplyEntity> childReply;
@CreationTimestamp
private Timestamp replyCreatedTime;
위 코드는 댓글에 답글이 생길 수 있도록 설계한 것이다.
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "replyId")
private ReplyEntity ReplyEntity;
@OneToMany(mappedBy = "ReplyEntity", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
private List<ReplyEntity> childReply;
이 부분이 답글 기능을 위한 코드이다.
먼저 replyId라는 replyEntity를 참조하는 외래키를 생성해준다. 이는 답글이 달리는 부모 댓글을 나타낸다.
이후 답글이 여러개 생성될 수 있도록 private List<ReplyEntity> childReply;를 작성한다.
마찬가지로 부모 댓글이 삭제되면 관련 답글들도 삭제되도록 설정하였다.
Entity 작성이 끝이 아니다.
Entity를 작성하였고, 이를 통해 자동으로 글 작성시 FK값이 저장이 되는 줄 알았다.
하지만 글을 여러 번 작성해도 계속 null값이 떴고, 구글링을 통해 새로운 사실을 알게 되었다.
글을 작성할 경우 FK값이 자동으로 저장이 되는 것이 아닌, 현재 로그인한 사용자의 상세 정보를 가져와 글 작성 로직에 포함해야 한다는 것이었다.
따라서 코드를 수정하였다.
1. 먼저 PostController를 수정한다.
postService.save(postDTO, userDetails.getId());
매개변수를 하나 추가해주고, userDetails.getId()를 통하여 사용자의 아이디를 불러온다.
2. PostService를 수정한다.
public void save(PostDTO postDTO, Long userId) {
PostEntity postEntity = PostDTO.toPostEntity(postDTO);
UserEntity userEntity = userRepository.findById(userId).orElse(null);
postEntity.setUserEntity(userEntity);
postRepository.save(postEntity);
}
먼저 구현해놓은 toPostEntity를 통하여 postEntity에 정보를 저장한다. 이후 Repositoty에서 id값을 찾아와 userEntity에 저장하고,
저장한 것을 setter를 통해 postEntity를 수정한다.
이후 postEntity를 save하면 끝!
