[Java/SpringBoot] JPA 어노테이션을 사용해서 Entity (엔티티) 만들기 (feat. Lombok)

minjung·2022년 11월 26일
0

💡Entity (엔티티) 란?

테이블을 객체로 표현한 자바 클래스이다. 어노테이션을 사용해서 엔티티와 테이블을 매핑할 수 있다.


💡Lombok 사용하기

Getter

모든 필드는 접근이 가능해야 한다.
Lombok을 사용해서 클래스 전체에 Getter를 설정한다.
Setter도 같이 설정하지 않는 이유는, 임의로 값을 입력하지 못하도록 하기 위함이다. 밑에 적겠지만, id값같은 경우는 자동으로 값이 입력되도록 해서 중복값을 가지지 못하도록 할 것이다.

@Getter
public class Article { }

ToString

그냥 쉽게 출력하기 위해 사용한다.

@ToString
public class Article { }

ToString.Exclude

순환 참조 에러를 해결할 수 있다.
Article 클래스 전체에 ToString 에너테이션이 있고, 그 안에는 ArticleComment 컬렉션이 있다.
그리고 ArticleComment 클래스에서 Article을 참조하고 있다면 순환참조 에러가 발생한다.

Article에서 ToString -> ArticleComment 컬렉션 -> ArticleComment에서 Article -> 다시 Article에서 ToString -> ...

이런 문제를 해결하기 위해서 한 쪽의 ToString을 끊어버리는 에너테이션을 붙이는 것이다.

  • Article.java
@ToString
@Entity
public class Article {

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

}
  • ArticleComment.java
@ToString
@Entity
public class ArticleComment {
    @Setter @ManyToOne(optional = false) private Article article;
}

💡JPA 사용하기

Table에 Index 사용하기

Table을 사용해서 인덱스를 잡을 수 있다.

  • Index (인덱스)
    인덱스는 키 값으로 행 데이터의 위치를 식별하는데 사용하는 기능이다.
    인덱스를 사용해서 조회 속도를 향상시킬 수 있다.
@Table(indexes = {
        @Index(columnList = "title"),
        @Index(columnList = "hashtag"),
        @Index(columnList = "createdAt"),
        @Index(columnList = "createdBy")
})
public class Article { }

인덱스 사이즈에는 제한이 있어서 컬럼의 데이터타입이 너무 클 경우에는 인덱스를 걸 수 없다.

Id

PK값을 주기 위해 Id 어노테이션을 사용한다.

@Id
private Long id;

GeneratedValue

Auto Increment(키값 자동증가)를 위해 GeneratedValue 어노테이션을 사용한다.

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

그 안에 strategy라는 옵션을 줄 수 있다. MySQLAuto IncrementIDENTITY 방식으로 만들어진다.

Auditing

  • CreatedDate
  • CreatedBy
  • LastModifiedDate
  • LastModifiedBy
@CreatedDate private LocalDateTime createdAt; // 생성일시
@CreatedBy private String createdBy; // 생성자
@LastModifiedDate private LocalDateTime modifiedAt; // 수정일시
@LastModifiedBy private String modifiedBy; // 수정자

최초 등록할 때, 수정할 때 데이터를 자동으로 입력해준다.

이를 위해 JpaConfig라는 클래스를 생성한 후 EnableJpaAuditing 어노테이션을 붙인다.
그리고 이 클래스 안에서 Auditing할 때 필요한 설정을 세팅해준다.

@EnableJpaAuditing
@Configuration
public class JpaConfig {

    @Bean
    public AuditorAware<String> auditorAware(){
        return () -> Optional.of("gamza"); // 스프링 시큐리티로 인증기능을 붙이게 될 때, 수정하자
    }

}

람다식으로 작성한 코드이고, 다른 방식으로도 작성 가능하다.
Optional로 내보내주어야 하고, 임의의 이름을 하나 넣어주었다. 이렇게 되면 Auditing할 때마다 이름은 gamza로 입력된다.
후에 스프링 시큐리티를 사용해서 사용자 정보를 가져올 수 있게 되면 수정하면 된다.

그리고 Auditing을 사용하면 Entity에도 에너테이션을 통해 Auditing을 사용한다는 것을 명시해주어야 한다.
이 코드를 적어주어야 제대로 동작한다.

@EntityListeners(AuditingEntityListener.class)
@Entity
public class Article {
	...
}

Column

기본적으로 Column 어노테이션은 생략 가능하다. 그래서 따로 적히지 않았다면 컬럼으로 간주한다.

  • nullable

not null 속성을 걸고 싶다면 Column 어노테이션을 사용해서 적용할 수 있다.
기본값은 true(널값허용)이기 때문에 널값을 허용하지 않겠다면 어노테이션을 적어주어야 한다.

@Column(nullable = false) private String content;
  • length

ERD 다이어그램을 만들 때 컬럼별로 데이터길이를 설정해주었을 것이다.
그때 정했던 길이를 length를 사용해서 설정해줄 수 있다.

@Setter @Column(nullable = false, length = 10000) private String content;
  • updatable

해당 필드는 update 불가하다는 것을 명시해준다.

@Column(nullable = false, updatable = false) private LocalDateTime createdAt;

ManyToOne

게시글 1개에 댓글 여러개가 있을 수 있는 것과 같은 관계일 때 사용한다.
이런 경우에는 댓글 엔티티에 작성한다.

  • optional
    필수값일 때는 optional 옵션이 false값이다.
@ManyToOne(optional = false) private Article article;
  • cascade
    데이터를 변경하거나 지웠을 때 관련된 객체가 영향을 받아야 하는가?와 관련된 옵션이다.
    댓글을 변경하거나 지웠을 때 관련된 게시글이 영향을 받을 필요가 없으므로, 이런 경우에는 cascade 옵션을 주지 않는다.
    기본값은 none이다.

OneToMany

게시글 1개에 댓글 여러개가 있을 수 있는 것과 같은 관계일 때 사용한다.
양방향 관계 설정. 이런 경우에는 게시글 엔티티에 작성한다.

List, Map, Set을 사용해서 맵핑할 수 있다.
중복을 허용하지 않겠다는 의미로 Set 사용
mappedBy로 이름을 지정하지 않으면 두 엔티티를 합친 것이 기본 이름이 된다.

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

💡기본생성자

모든 JPA 엔티티들은 기본생성자를 가지고 있어야 한다.
기본생성자는 평소에 오픈하지 않을 것이기 때문에 protected로 하면 된다. public으로 할 필요가 없다.

protected Article() {}

💡Spring 사용하기

DateTimeFormat

날짜와 관련된 컬럼에 파싱이 잘 되게 하기 위해서 사용할 수 있다.
iso 클래스를 사용하는 방법과 pattern으로 문자열을 넣는 방법이 있다.

@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
@CreatedDate
@Column(nullable = false, updatable = false) private LocalDateTime createdAt

💡완성된 전체 Entity 코드

  • Article (게시글)
package com.fastcampus.projectboard.domain;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
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 javax.persistence.*;
import java.time.LocalDateTime;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;

@Getter
@ToString
@Table(indexes = {
        @Index(columnList = "title"),
        @Index(columnList = "hashtag"),
        @Index(columnList = "createdAt"),
        @Index(columnList = "createdBy")
})
@EntityListeners(AuditingEntityListener.class)
@Entity
public class Article {

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

    @Setter @Column(nullable = false) private String title; // 제목
    @Setter @Column(nullable = false, length = 10000) private String content; // 본문

    @Setter private String hashtag; // 해시태그

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

    @CreatedDate @Column(nullable = false) private LocalDateTime createdAt; // 생성일시
    @CreatedBy @Column(nullable = false, length = 100) private String createdBy; // 생성자
    @LastModifiedDate @Column(nullable = false) private LocalDateTime modifiedAt; // 수정일시
    @LastModifiedBy @Column(nullable = false, length = 100) private String modifiedBy; // 수정자

    protected Article() {}

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

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

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

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

  • ArticleComment (댓글)
package com.fastcampus.projectboard.domain;

import lombok.Getter;
import lombok.Setter;
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 javax.persistence.*;
import java.time.LocalDateTime;
import java.util.Objects;

@Getter
@ToString
@Table(indexes = {
        @Index(columnList = "content"),
        @Index(columnList = "createdAt"),
        @Index(columnList = "createdBy")
})
@EntityListeners(AuditingEntityListener.class)
@Entity
public class ArticleComment {

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

    @Setter @ManyToOne(optional = false) private Article article; // 게시글 (ID)
    @Setter @Column(nullable = false, length = 500) private String content; // 본문

    @CreatedDate @Column(nullable = false) private LocalDateTime createdAt; // 생성일시
    @CreatedBy @Column(nullable = false, length = 100) private String createdBy; // 생성자
    @LastModifiedDate @Column(nullable = false) private LocalDateTime modifiedAt; // 수정일시
    @LastModifiedBy @Column(nullable = false, length = 100) private String modifiedBy; // 수정자

    protected ArticleComment() {}

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

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

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

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

기본생성자 밑에 오버로딩 생성자로 주입하는 부분이랑 equals and hashCode 부분도 추가적으로 정리해야 하는데 아직 제대로 이해를 못해서, 추후에 추가로 정리하기로!

0개의 댓글