상똥이의 Back-End 공부방

[Board Project] 5. 데이터베이스 연동하기(2) (annotation/ JPA) 본문

프로젝트/게시판 만들기

[Board Project] 5. 데이터베이스 연동하기(2) (annotation/ JPA)

상똥백 2023. 9. 5. 22:19

목차

1. Entity 구성

2. Entity 기본 기능 구현

3. 동일성 검사

 

[1. Entity 구성]

1. JPA 연동

- 자동화를 위해 JPA를 연동해야한다. 한다

 (1) board_practice 패키지 안에 config 패키지를 만든다

 (2) config 패키지 안에 클래스 JpaConfig를 만든다

 (3) Jpa를 활성화하기 위해 이름 또는 타입을 기반으로 필요한 빈을 찾아 자동으로 반환 하는 @EnableJpaAuditing을 클래스 전체에 삽입한다. 엔티티가 데이터베이스에 저장되거나 업데이트될 때 생성 시간과 수정 시간을 자동으로 입력시켜준다.

 (4) AutoAwared를 사용해 엔터티의 생성자와 수정자를 자동으로 가져와야 하는데 이 메서드는 @Bean을 사용해야 한다.

 (5) 지정된 클래스와 그 하위 클래스의 모든 빈을 스캔할 수 있도록 해주는 @Configuration을 삽입한다.

 (6) 생성자와 수정자는 현 단계에서 만들기엔 한계가 있으므로 대충 아무 아이디로 결정해두고 후에 수정할 TODO로 남겨둔다

코드 (접은 글)

더보기
package com.fastcampus.projectbord.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

import java.util.Optional;

@EnableJpaAuditing
@Configuration
public class JpaConfig {

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

}

 

2. Article 클래스에 어노테이션 삽입

 (1) @Getter : 모든 필드에 접근이 가능해지기 위해 전체에 붙여준다

 (2) @ToString : 출력을 원활히 하기 위해 전체에 붙여준다

 (3) @Table : @Index를 사용해 검색도구를 지정하기 위해 붙여준다

  - index : title, hashtag, createdAt, createdBy

  - content(본문 내용)는 index로 검색하기에 내용이 너무 광활하므로, MySQL에서 기능을 추가한다

 (4) @Entity : 만들어둔 도메인을 엔터티로 만들기 위해 전체에 붙여준다

 (5) @AuditingEntityListener : 클래스를 읽으며 수집할 때 엔터티를 자동적으로 수집하도록 돕는 어노테이션

코드(접은 글)

더보기
package com.fastcampus.projectbord.domain;

import lombok.Getter;
import lombok.ToString;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;

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

(생략)

 

3. Article 도메인 각각에 애노테이션 삽입

(1) id 객체

 - @id @GeneratedValue(Strategy = GenerationType.IDENTITY) : id 객체가 기본키임을 명시한다. 

(2) title 객체

 - @Setter : 도메인에서 수정이 가능하도록 한다

 - @Column(nullable=false) : null값을 갖지 못하도록 한다

(3) content 객체

 - @Setter : 도메인에서 수정이 가능하도록 한다 

(4) hashtag 객체

 - @Setter : 도메인에서 수정이 가능하도록 한다

(5) createdAt 객체

 - @createdDate : 생성 날짜를 반환한다

 - @Column(nullable=false) : null값을 갖지 못하도록 한다

(6) createdBy 객체

 - @createdBy : 생성주체를 반환한다

 - @Column(nullable=false, length=100) : null값을 갖지 못하도록 한다, 길이를 100으로 제한한다

(7) modifiedAt 객체

 - @LastModifiedDate : 마지막 수정일자를 반환한다

 - @Column(nullabl=false) : null값을 갖지 못하도록 한다

(8) modifiedBy 객체

 - @LastModifiedBy : 마지막 수정자를 반환한다

 - @Column(nullable=false, length=100) : null값을 갖지 못하도록 한다, 길이를 100으로 제한한다

※ Setter를 도메인 각각에 거는 이유 : Article class전체에 걸면 도메인에서 id객체에 접근하여 id를 바꾸는 것이 가능해지는데, 그런 기능은 넣고 싶지 않기 때문이다.

코드(접은 글)

더보기
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;    // 게시글 작성자 아이디, PK

    @Setter
    @Column(nullable = false)
    private String title;   // 게시글 제목

    @Setter
    @Column(nullable = false, length = 10000)
    private String content; //게시글 내용

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

    @CreatedDate
    @Column(nullable = false)
    private LocalDateTime createdAt;    // 게시글 작성일시

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

    @LastModifiedDate
    private LocalDateTime modifiedAt;   // 게시글 수정일시

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

 

5. ArticleComment 클래스에 어노테이션 삽입

 (1) @Getter : 모든 필드에 접근이 가능해지기 위해 전체에 붙여준다

 (2) @ToString : 출력을 원활히 하기 위해 전체에 붙여준다

 (3) @Table : @Index를 사용해 검색도구를 지정하기 위해 붙여준다

  - index : content, createdAt, createdBy

 (4) @Entity : 만들어둔 도메인을 엔터티로 만들기 위해 전체에 붙여준다

코드(접은 글)

package com.fastcampus.projectbord.domain;

import lombok.Getter;
import lombok.ToString;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;

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

 

6. ArticleComment 도메인 각각에 애노테이션 삽입

(1) id 객체

 - @id @GeneratedValue(Strategy = GenerationType.IDENTITY) : id 객체가 기본키임을 명시한다. 

(2) Article 객체

 - @Setter : 도메인에서 수정이 가능하도록 한다

 - @ManyToOne(Optional = false) :  N:1관계로, 게시글 하나에 N개의 댓글이 달릴 수 있으며 Optional=false는 댓글이 아예 없을 수 있음을 뜻한다.

(3) content 객체

 - @Setter : 도메인에서 수정이 가능하도록 한다

 - @Column(nullable=false, length=500) : null값을 갖지 못하게 하며 길이는 500으로 제한한다

(4) createdAt 객체

 - @createdDate : 생성 날짜를 반환한다

 - @Column(nullable=false) : null값을 갖지 못하도록 한다

(5) createdBy 객체

 - @createdBy : 생성주체를 반환한다

 - @Column(nullable=false, length=100) : null값을 갖지 못하도록 한다, 길이를 100으로 제한한다

(6) modifiedAt 객체

 - @LastModifiedDate : 마지막 수정일자를 반환한다

 - @Column(nullabl=false) : null값을 갖지 못하도록 한다

(7) modifiedBy 객체

 - @LastModifiedBy : 마지막 수정자를 반환한다

 - @Column(nullable=false, length=100) : null값을 갖지 못하도록 한다, 길이를 100으로 제한한다

코드(접은 글)

	@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; // 수정자

 

[2. Entity 기본 기능 구현]

1. Article 기본 기능 구현

 (1) 기본 생성자를 제작한다

  - 평소에 열어보지 않으므로 protected로 만들어준다 (기본 생성자는 private을 사용할 수 없다)

 (2) 속성을 생성하는 생성자를 만든다

  - 아이디와 메타데이터를 제외한, 사용자가 작성하는 title, content, hashtag만 생성하도록 구현한다

  - private으로 막아두고 팩토리로 제공한다

 (3) 팩토리 메소드 작성

  - Article을 생성하고자 할 때 필요한 도메인을 명시

코드(접은 글)

더보기
   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);
    }
    //팩토리 메소드

 

2. ArticleComment 기본 기능 구현

더보기
    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);
    }

 

[3. 동일성 검사]

1. Article

- 후에 게시판 화면을 게시글로 구성할 때, List 등 컬렉션을 사용할 수 있음

- 컬렉션으로 게시글을 사용하려면 중복 제거, 정렬 등의 기능이 필요

- equal hashcode 필요

- lombok 사용시 모든 필드를 비교하므로 비효율적 → id만 사용해서 비교

- 따라서 java.util.Objects.equals and hashCode 기능 사용

코드 (접은 글)

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

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

 

2. ArticleComment

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

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

 

[4. 실행]

여기까지 작성 후 실행하면 board 데이터베이스에 테이블이 생성된다

이렇게!!

 

 

마무리~~~

최종 코드 (접은 글)

더보기

Article

package com.fastcampus.projectbord.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.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 article)) return false;
        return id != null && id.equals(article.id);
    }

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

}

ArticleComment

package com.fastcampus.projectbord.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 {

JpaConfig

package com.fastcampus.projectbord.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

import java.util.Optional;

@EnableJpaAuditing
@Configuration
public class JpaConfig {

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

}