[Spring] 스프링 부트를 이용한 게시판 프로젝트 - Domain(model)

전주은·2023년 1월 4일
0
post-thumbnail

Model 설계하기

들어가기전에..

Domain은 비즈니스 로직과 데이터를 포함하는 계층적인 구조를 가진 객체 모델입니다. 이 객체 모델은 프로젝트에서 사용되는 데이터를 추상화하고, 서비스와 데이터베이스 간의 중개자 역할을 합니다. 즉, Domain은 애플리케이션의 핵심적인 요소 중 하나로, 데이터의 저장, 조회, 수정, 삭제 등과 같은 데이터 조작을 담당합니다.

예를 들어, 게시판 프로젝트에서는 게시글, 댓글, 회원 등의 도메인을 정의할 수 있습니다. 게시글 도메인은 게시글의 제목, 내용, 작성일, 수정일 등의 속성을 포함하며, 댓글 도메인은 댓글의 작성자, 내용, 작성일 등의 속성을 포함합니다. 이러한 도메인 모델은 프로젝트에서 필요한 데이터를 관리하며, 비즈니스 로직과 데이터 조작을 처리하기 위한 메소드를 제공합니다.

따라서, Spring Boot를 이용한 게시판 프로젝트에서는 Domain 객체를 잘 설계하고 구현하는 것이 중요합니다. 이를 통해 데이터베이스와의 상호작용이 원활해지고, 유지보수성과 확장성이 향상됩니다.

👩회원

package com.koreait.projectboard.domain;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import javax.persistence.*;
import java.util.Objects;

@Getter
@ToString
@Table(indexes = {
        @Index(columnList = "email", unique = true),
        @Index(columnList = "createdAt"),
        @Index(columnList = "createdBy")
})
@Entity
public class UserAccount {
    @Id
    @Column(length = 50)
    private String userId;

    @Setter @Column(nullable = false) private String userPassword;
    @Setter @Column(length = 100) private String email;
    @Setter @Column(length = 100) private String nickname;
    @Setter
    private String memo;

    protected UserAccount() {}

    public UserAccount(String userId, String userPassword, String email, String nickname, String memo) {
        this.userId = userId;
        this.userPassword = userPassword;
        this.email = email;
        this.nickname = nickname;
        this.memo = memo;
    }

    public static UserAccount of(String userId, String userPassword, String email, String nickname, String memo) {
        return new UserAccount(userId, userPassword, email, nickname, memo);
    }

    @Override
    public int hashCode() {
        return Objects.hash(userId);
    }

    @Override
    public boolean equals(Object obj) {
        if(this == obj) return true;
        if(!(obj instanceof UserAccount userAccount)) return false;
        return userId != null && userId.equals(userAccount.userId);
    }
}

위 코드는 Spring Boot를 이용한 게시판 프로젝트에서 사용되는 UserAccount 도메인의 예시입니다. 이 도메인은 사용자 계정 정보를 저장하고 관리하는 역할을 합니다.

각 속성은 @Column 어노테이션을 사용하여 데이터베이스 테이블의 컬럼과 매핑되며, @Getter와 @Setter 어노테이션을 이용하여 getter와 setter 메소드를 자동으로 생성합니다. 또한, @ToString 어노테이션을 사용하여 toString() 메소드를 자동으로 생성합니다.

데이터베이스 테이블의 생성과 관리는 JPA의 @Entity 어노테이션을 사용하여 정의됩니다. UserAccount 클래스는 AuditingFields 클래스를 상속하며, 생성일, 수정일, 생성자, 수정자 정보를 저장합니다. @Table 어노테이션을 사용하여 데이터베이스 테이블 이름과 인덱스를 정의할 수 있습니다.

hashCode() 메소드와 equals() 메소드를 오버라이딩하여 객체의 동등성을 비교할 수 있도록 합니다.

이와 같이 Spring Boot 프로젝트에서 사용되는 Domain 객체는 비즈니스 로직과 데이터 조작을 담당하므로, 데이터베이스와의 상호작용을 원활하게 하기 위해 적절하게 설계해야 합니다.

✅ 재사용을 위한 인터페이스

package com.koreait.projectboard.domain;

import lombok.Getter;
import lombok.ToString;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import org.springframework.format.annotation.DateTimeFormat;

import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;

@Getter
@ToString
@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
public abstract class AuditingFields {

    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
    @CreatedDate
    @Column(nullable = false) private LocalDateTime createdAt; // 생성일시

    @CreatedBy
    @Column(nullable = false, length = 100) private String createdBy; // 생성자

    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
    @LastModifiedDate
    @Column(nullable = false) private LocalDateTime modifiedAt; // 수정일시

    @LastModifiedBy
    @Column(nullable = false, length = 100) private String modifiedBy; // 수정자
}

위 코드는 Spring Boot 프로젝트에서 사용되는 Entity 클래스에서 공통으로 사용되는 필드들(생성일시, 생성자, 수정일시, 수정자)을 관리하기 위한 클래스인 AuditingFields 입니다.

이 클래스는 @MappedSuperclass 어노테이션을 사용하여 데이터베이스 테이블로 매핑되지 않으며, @EntityListeners 어노테이션을 사용하여 AuditingEntityListener 클래스가 엔티티 이벤트를 처리하도록 합니다.

@EntityListeners 어노테이션을 사용하여 AuditingEntityListener 클래스가 엔티티 이벤트를 처리하도록 함으로써, 엔티티가 생성 및 수정될 때 자동으로 필드들의 값을 설정할 수 있습니다.

따라서, 이 클래스를 상속받은 엔티티 클래스에서는 @CreatedDate, @CreatedBy, @LastModifiedDate, @LastModifiedBy 어노테이션을 사용하여 각 필드에 값을 설정할 필요가 없으며, 필드 값은 자동으로 설정됩니다.

이렇게 AuditingFields 클래스를 사용함으로써, 엔티티 클래스에서 공통으로 사용되는 필드들의 값을 일관되게 관리할 수 있으며, 개발자는 각 필드들의 값을 설정하는 코드를 작성하지 않아도 되므로 코드의 반복을 줄일 수 있습니다.

📄게시글

package com.koreait.projectboard.domain;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import javax.persistence.*;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;


@Getter
@ToString(callSuper = true)
@Table(indexes = {
        @Index(columnList = "title"),
        @Index(columnList = "hashtag"),
        @Index(columnList = "createdAt"),
        @Index(columnList = "createdBy")
})
@Entity
@EqualsAndHashCode
public class Article extends AuditingFields{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Setter @ManyToOne(optional = false) @JoinColumn(name="userId") private UserAccount userAccount;    // 유저 정보
    @Setter @Column(nullable = false) private String title; // 제목
    @Setter @Column(nullable = false, length = 10000) private String content; // 본문
    @Setter private String hashtag; // 해시태그

    @ToString.Exclude
    @OrderBy("createdAt DESC")
    @OneToMany(mappedBy = "article", cascade = CascadeType.ALL)
    private final Set<ArticleComment> articleComments = new LinkedHashSet<>();

    protected Article() {}

    private Article(UserAccount userAccount, String title, String content, String hashtag) {
        this.userAccount = userAccount;
        this.title = title;
        this.content = content;
        this.hashtag = hashtag;
    }

    public static Article of(UserAccount userAccount, String title, String content, String hashtag) {
        return new Article(userAccount, title, content, hashtag);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

    @Override
    public boolean equals(Object obj) {
        if(this == obj) return true;
        if(!(obj instanceof Article article)) return false;
        return id != null && id.equals(article.id);
    }
}

위 코드는 Spring Boot 기반 게시판 프로젝트에서 사용되는 게시글(Article) 도메인 객체입니다.

@Entity 어노테이션을 이용해 JPA Entity 클래스임을 선언하고, @Table 어노테이션을 이용해 테이블에 대한 정보를 설정합니다. @Id 어노테이션을 이용해 Entity의 식별자인 id를 설정하고, @GeneratedValue 어노테이션을 이용해 id 필드의 값이 자동으로 생성되도록 합니다.

게시글의 작성자 정보인 UserAccount 객체는 @ManyToOne 어노테이션을 이용해 다대일(N:1) 관계를 설정하고, @JoinColumn 어노테이션을 이용해 외래키로 사용될 userId 필드를 지정합니다.

게시글의 제목(title), 본문(content), 해시태그(hashtag) 정보를 담는 필드들은 각각 @Column 어노테이션을 이용해 컬럼 정보를 설정합니다.

@OneToMany 어노테이션을 이용해 게시글과 댓글 간의 일대다(1:N) 관계를 설정하고, mappedBy 속성으로 양방향 매핑에서 반대쪽 엔티티에서 해당 필드를 참조하도록 지정합니다. 그리고, cascade 속성을 이용해 게시글이 삭제될 때 해당 게시글의 댓글도 함께 삭제되도록 설정합니다.

또한, AuditingFields 클래스를 상속받아 게시글의 생성일시(createdAt), 생성자(createdBy), 수정일시(modifiedAt), 수정자(modifiedBy) 정보를 담는 필드를 추가하고, @EntityListeners(AuditingEntityListener.class) 어노테이션을 이용해 엔티티 리스너를 등록합니다. 이를 통해 JPA에서 제공하는 @CreatedDate, @CreatedBy, @LastModifiedDate, @LastModifiedBy 어노테이션을 이용해 각각의 필드들이 자동으로 설정되도록 합니다.

@ToString, @Getter, @Setter 어노테이션을 이용해 각 필드의 값을 문자열로 출력하도록 설정하고, 필요한 경우 @EqualsAndHashCode 어노테이션을 이용해 객체의 동등성과 해시코드를 생성합니다.

💬댓글

package com.koreait.projectboard.domain;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import javax.persistence.*;
import java.util.Objects;

@Getter
@ToString(callSuper = true)
@Table(indexes = {
        @Index(columnList = "content"),
        @Index(columnList = "createdAt"),
        @Index(columnList = "createdBy")
})
@Entity
public class ArticleComment extends AuditingFields{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Setter @ManyToOne(optional = false) private Article article;    // 게시글(id)
    @Setter @ManyToOne(optional = false) @JoinColumn(name="userId") private UserAccount userAccount; // 유저정보
    @Setter @Column(nullable = false, length = 500) private String content;    // 본문

    protected ArticleComment() {}

    private ArticleComment(Article article, UserAccount userAccount, String content){
        this.article = article;
        this.userAccount = userAccount;
        this.content = content;
    }

    public static ArticleComment of(Article article, UserAccount userAccount, String content){
        return new ArticleComment(article, userAccount, content);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

    @Override
    public boolean equals(Object obj) {
        if(this == obj) return true;
        if(!(obj instanceof ArticleComment articleComment)) return false;
        return id != null && id.equals(articleComment.id);
    }
}

이 코드는 Spring Data JPA를 사용하여 게시판의 게시글과 댓글을 관리하기 위한 도메인 모델 클래스입니다.

AuditingFields 클래스는 JPA Entity 클래스들이 생성, 수정일시와 생성자, 수정자 정보를 가질 수 있도록 도와주는 추상 클래스입니다. 이 클래스를 상속받으면 자동으로 생성, 수정일시와 생성자, 수정자 정보가 저장됩니다.

Article 클래스는 게시글 정보를 저장하는 클래스입니다. UserAccount와 연결되어 있으며, 제목, 내용, 해시태그 정보를 가집니다. 또한 ArticleComment 객체들과 OneToMany 관계를 맺습니다.

ArticleComment 클래스는 댓글 정보를 저장하는 클래스입니다. Article과 UserAccount와 연결되어 있으며, 내용 정보를 가집니다. 댓글은 Article 객체와 ManyToOne 관계를 맺습니다.

이 코드는 Lombok 애노테이션을 사용하여 Getter, Setter, ToString, EqualsAndHashCode 메소드를 자동 생성하고, JPA 애노테이션을 사용하여 DB에 저장될 스키마를 정의합니다. 또한 AuditingFields 추상 클래스를 상속하여 생성, 수정일시와 생성자, 수정자 정보를 자동 저장할 수 있도록 합니다.

0개의 댓글