본문 바로가기

Programming/SpringBoot

[SpringBoot] 간단한 게시판 만들기 #3 - domain(Entity) 구현

반응형

패키지명은 보통 domain 혹은 entity로 표기하며, 내부 Class or Enum 파일들은 DB 처리용으로 DB Table과 Mapping된다.

 

Entity 객체는 보통 DTO(Data Transfer Object)와 분리하게 된다.

 

domain 패키지에는 Board, Time, Role, User 4개의 파일이 존재하게 된다.

 

Board : 게시판 글 정보들을 모아놓은 Board 테이블

 

Time : 데이터 조작 시 자동으로 날짜를 수정해주는 JPA의 Auditing 기능을 사용한다.

         이 Time Entity를 만들어 놓고 다른 Entity(Board, ..)로부터 상속받아서 사용하게 된다.

 

Role : Spring Security 중 사용자의 권한을 Enum 클래스로 만들어 관리한다.

(Enum : 열거형 타입임을 의미하며 집합이 갖는 값이 한정되어 있고, 다른 값을 허용하지 않도록 하기 위해 사용)

 

User : Google OAuth2 로그인 한 사용자들에 대한 정보를 저장하기 위한 User 테이블


[1] application.yml에서의 JPA 설정 부분

 

 

- datasource에 어떤 Driver를 사용하며, DB URL, Username, Password 등을 설정할 수 있다.

 

- JPA Database Platfor으로 org.hibernate.dialect.MySQL5InnoDBDialect (InnoDB)를 지정

- OSIV(Open Session In View)는 웹 요청이 완료될 때까지 동일한 EntityManager를 갖도록 해준다.

   >> 성능과 확장성 면에서 안좋다고 하여 False

- show-sql : JPA 실행 Query를 콘솔에 출력한다.

- format_sql : JPA가 실행하는 Query를 콘솔에서 보기 좋게 해준다.

- ddl-auth : 데이터베이스 초기화 전략으로, update(변경된 스키마만 반영)하도록 설정

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/spring_board?serverTimezone=Asia/Seoul&characterEncoding=UTF-8
    username: root
    password: dkagh1.
  profiles:
    include: oauth

jpa:
  database-platform: org.hibernate.dialect.MySQL5InnoDBDialect  # JPA 데이터베이스 플랫폼 (InnoDB)
  open-in-view: false
  show-sql: true
  hibernate:
    format_sql: true
    ddl-auto: update  # none, update, create, create-drop, validate

 

[2] Builder 패턴을 제공하는 Lombok

보통의 경우, Java에서 Data Model을 정의할 때 getter, setter 메서드로 데이터에 접근하는 형태가 일반적이다.

 

 

#### 예제 시작 ####

package shop.pingping2.board;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing //Base_Entity에 있는 날짜 자동 입력 활성화
@SpringBootApplication
public class BoardApplication {
   public static void main(String[] args) {
      SpringApplication.run(BoardApplication.class, args);
   }
}

 

lombok Annotation 라이브러리를 사용하면 Annotation을 추가하는 것만으로 get, set 메서드를 생성할 필요 없이 동일한 효과를 발휘하는 코드를 작성할 수 있다.

class Salary{
	private int id;
    private String name;
    private String email;
    
    public void setId(int id){
    	this.id = id;
    }
    
    public int getId(){
    	return this.id;
    }

    public void setName(String name){
    	this.name = name;
    }
    
    public int getName(){
    	return this.name;
    }
    
    public void setEmail(String email){
    	this.email= email;
    }
    
    public int getEmail(){
    	return this.email;
    }
}

 

1. @Getter, @Setter

자동으로 Getter, Setter가 생성된다.

 

2. AllArgsConstructor

생성된 모든 멤버변수(username, password, email)를 인자로 받는 생성자를 만드는 Annotation

 

3. @RequiredArgsConstructor

Class를 만들 때 필수적인 멤버 변수(final 변수)를 인자로 받는 생성자를 만든다. (객체의 멤버 변수 값이 수정될 일이 없는 클래스일 경우)

 

4. @NoArgsConstructor

빈 생성자를 만드는 어노테이션

 

5. @Builder

위에서 사용한 생성자 Annotation을 사용하면 객체를 만들 때 항상 정해진 순서에 맞춰 필요한 모든 Parameter를 넣어줘야 한다.

하지만 Builder를 사용하게 되면 순서와 상관 없이 만들 뿐만 아니라 입력하지 않은 값은 자동으로 Null을 넣어 생성하거나 할 수 있다.

 

 

Member.java

@Data
@NoArgsConstructor
public class Member {
    private int id;
    private String username;
    private String password;
    private String email;

    @Builder
    public Member(int id, String username, String password, String email) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.email = email;
    }
}

 

ControllerTest.java

@RestController
public class HttpControllerTest {

    private static final String TAG = "HttpControllerTest : ";

    @GetMapping("/http/lombok")
    public String lombokTest() {
        Member m = Member.builder().username("deannn").password("123").email("deannn@test.com").build();
        System.out.println(TAG + "getter : " + m.getId());
        m.setId(5000);
        System.out.println(TAG + "setter : " + m.getId());
        System.out.println(TAG + "getter : " + m.getUsername());
        m.setUsername("park");
        System.out.println(TAG + "setter : " + m.getUsername());
        return "lombok test finished";
    }

 

#### 예제 끝 ####


 

shop.pingping2.board.domain.Board

package shop.pingping2.board.domain;

import lombok.*;
import org.springframework.util.Assert;

import javax.persistence.*;

// Board : 실제 DB와 매칭될 클래스 (Entity Class)

// JPA에서는 프록시 생성을 위해 기본 생성자를 반드시 하나 생성해야 한다.
// 생성자 자동 생성 : NoArgsConstructor, AllArgsConstructor
// NoArgsConstructor : 객체 생성 시 초기 인자 없이 객체를 생성할 수 있다.

@NoArgsConstructor(access = AccessLevel.PROTECTED)  // 외부에서의 생성을 열어 둘 필요가 없을 때 / 보안적으로 권장된다.
@Getter
@Entity
@Table(name = "board")  // 이거 보고 테이블 생성
public class Board extends Time {

    @Id // PK Field
    @GeneratedValue(strategy= GenerationType.IDENTITY)  // PK의 생성 규칙
    private Long id;

    @Column(length = 10, nullable = false)
    private String writer;

    @Column(length = 100, nullable = false)
    private String title;

    @Column(columnDefinition = "TEXT", nullable = false)
    private String content;

    @OneToOne(fetch = FetchType.LAZY)
    private User user;

    // Java 디자인 패턴, 생성 시점에 값을 채워줌
    @Builder
    public Board(Long id, String title, String content, String writer, User user) {
        // Assert 구문으로 안전한 객체 생성 패턴을 구현
        Assert.hasText(writer, "writer must not be empty");
        Assert.hasText(title, "title must not be empty");
        Assert.hasText(content, "content must not be empty");

        this.id = id;
        this.writer = writer;
        this.title = title;
        this.content = content;
        this.user = user;
}

 

Getter 애노테이션에 의해 Getter 메서드가 생성되었음

 

 

@NoArgsConstructor(access = AccessLevel.PROTECTED)  // 외부에서의 생성을 열어 둘 필요가 없을 때 / 보안적으로 권장된다.
@Getter
@Entity
@Table(name = "board")  // 이거 보고 테이블 생성
public class Board extends Time {

@NoArgsConstructor(access = AccessLevel.PROTECTED)

생성자의 접근 제어를 PROTECTED로 설정 (무분별한 객체 생성에 대해 한번 더 체크할 수 있는 수단이 된다.)

 

@Getter

getId() 메서드를 사용하기 위함 (Lombok이 Getter 메서드를 알아서 만들어 준다.)

 

@Entity

해당 Annotation을 선언하면 JPA가 관리하게 된다. 이를 통해 JPA가 DB의 Table과 상호 작용할 수 있게 되며, 개발자는 객체 지향적으로 DB Table 및 Query문을 수행할 수 있게 된다.

 

@Table

맵핑할 Table을 지정하며 name 속성을 통해 맵핑할 테이블의 이름을 지정해준다.

 

 

 

@Id // PK Field
@GeneratedValue(strategy= GenerationType.IDENTITY)  // PK의 생성 규칙
private Long id;

@Id

해당 Property가 Primary Key(주키)의 역할을 한다는 것을 나타낸다.

 

@GeneratedValue

Primary Key의 값을 위한 자동 생성 전략을 명시하는데 사용한다.

 

 

 

@OneToOne(fetch = FetchType.LAZY)
private User user;

Board가 필드값으로 갖고 있는 User 도메인을 1:1 관계로 설정하는 Annotation이다.

lazy : User 객체를 조회하는 시점이 아닌 객체가 실제 사용될 때 조회한다는 뜻이다. lazy와 eager가 있다.

 

 

 

 

// Java 디자인 패턴, 생성 시점에 값을 채워줌
    @Builder
    public Board(Long id, String title, String content, String writer, User user) {
        // Assert 구문으로 안전한 객체 생성 패턴을 구현
        Assert.hasText(writer, "writer must not be empty");
        Assert.hasText(title, "title must not be empty");
        Assert.hasText(content, "content must not be empty");

        this.id = id;
        this.writer = writer;
        this.title = title;
        this.content = content;
        this.user = user;

해당 Board Entity가 Builder 패턴을 사용함을 @Builder 애노테이션을 통해 나타낸다.

 

@Builder 애노테이션을 사용하면, 추후 객체를 생성할 때 아래의 형태로 간단하게 객체를 생성할 수 있다.

=> builder 메서드가 제공됌

 

{Class} {obj} {Class}.builder()

           .id(id)

           .name(name)

           . ...

           . ...

           .build();

 

 

 

Assert.hasText(writer, "writer must not be empty");
Assert.hasText(title, "title must not be empty");
Assert.hasText(content, "content must not be empty");

Assert 구문으로 안전한 객체 생성 패턴을 구현

writer, title, content 객체가 전달되지 않을 경우 Exception을 발생시킨다.

 

 

 

this.id = id;
this.writer = writer;
this.title = title;
this.content = content;

생성자를 통해 해당 Class의 Instance 변수를 정의

(Python의 self.name = name, self.email = email, .. 로 생각하면 된다.)

 

 

 

 

shop.pingping2.board.domain.Role

package shop.pingping2.board.domain;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum Role {
    GUEST("ROLE_GUEST", "손님"),
    USER("ROLE_USER", "일반 사용자");

    private final String key;
    private final String title;
}

public enum Role

>> enum 타입임을 의미한다.

 

 

 

 

shop.pingping2.board.domain.Time

package shop.pingping2.board.domain;

import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

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

@Getter
@MappedSuperclass   // 클래스가 만들어지지 않는 기초 클래스라는 Annotation
@EntityListeners(value = {AuditingEntityListener.class})    // Entity의 변화를 감지하는 리스너
public abstract class Time {  // 상속

    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdDate;

    @LastModifiedDate
    @Column
    private LocalDateTime modifiedDate;
}

 

 

 

JPA Auditing을 활성화할 수 있도록 BoardApplication 클래스에 활성화 애노테이션을 추가해준다.

package shop.pingping2.board;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing //Base_Entity에 있는 날짜 자동 입력 활성화
@SpringBootApplication
public class BoardApplication {
   public static void main(String[] args) {
      SpringApplication.run(BoardApplication.class, args);
   }
}

 

 

 

 

shop.pingping2.board.domain.User

package shop.pingping2.board.domain;

import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.*;

@Getter
@Entity
@Table(name = "user")
@NoArgsConstructor(access = AccessLevel.PROTECTED)  // 외부에서의 생성을 열어 둘 필요가 없을 때 / 보안적으로 권장된다.
public class User extends Time{

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

    @Column(nullable = false)
    private String name;

    @Column(nullable = false)
    private String email;

    @Column
    private String picture;

    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private Role role;

    @Builder
    public User(String name, String email, String picture, Role role){
        this.name = name;
        this.email = email;
        this.picture = picture;
        this.role = role;
    }

    public User update(String name, String picture) {
        this.name = name;
        this.picture = picture;
        return this;
    }

    public String getRoleKey() {
        return this.role.getKey();
    }
}

 

 

 

 

Ref

https://velog.io/@deannn/Spring-Boot-maven-%EA%B0%9C%EB%85%90-Lombok-%EC%84%B8%ED%8C%85

https://dev-overload.tistory.com/26

 

반응형