본문 바로가기
코드잇 스프린트 : 프론트엔드 단기심화 5기 (백엔드 협업)

스프링 부트의 기본 기능 사용하기

by 아임제니퍼 2024. 11. 23.

 

https://wikidocs.net/book/7601

 

점프 투 스프링부트

"점프 투 스프링부트"는 "Spring Boot Board(SBB)"라는 이름의 게시판 서비스를 만들어가는 과정을 설명한 스프링부트 입문서이다. 자바 설치부터 시작하여 서비스 운…

wikidocs.net

 

이론적인 내용을 참고하여 서술하였습니다.


 

1. JPA로 데이터베이스 사용하기

1-1. H2 데이터베이스 설치하기

build.gradle

spring:
  h2:
    console:
      enabled: true
      path: /h2-console

  datasource:
    url: jdbc:h2:mem:~/mukitListApplication
    driver-class-name: org.h2.Driver
    username: sa
    password: ''
  • spring.h2.console.enabled : H2 콘솔에 접속할 것인지를 묻는 항목이다.
  • spring.h2.console.path : H2 콘솔로 접속하기 위한 URL 경로이다.
  • spring.datasource.url : 데이터베이스에 접속하기 위한 경로이다.
  • spring.datasource.driverClassName : 데이터베이스에 접속할 때 사용하는 드라이버 클래스명이다.
  • spring.datasource.username : 데이터베이스의 사용자명이다(사용자명으로 기본값인 sa로 설정한다.).
  • spring.datasource.password : 데이터베이스의 비밀번호이다(여기서는 로컬에서 개발 용도로만 사용하므로 비밀번호를 설정하지 않고 비워 두었다.).

H2 데이터베이스의 저장 위치와 작동 방식

더보기

jdbc:h2:~local vs jdbc:h2:mem:~/mukitListApplication

 

jdbc:h2:~local

디스크 기반 저장소를 사용하여, 데이터베이스의 내용이 지속

jdbc:h2:mem:~/mukitListApplication

메모리 기반 저장소를 사용하여, 애플리케이션 종료 시 데이터가 사라짐

1-2. JPA 환경 설정하기

build.gradle

  jpa:
    hibernate:
      ddl-auto: create
    show-sql: true
    properties:
      hibernate:
        format-sql: true
  • ddl-auto : 엔티티르르 기준으로 데이터의 테이블으르 생성하는 규칙을 설정한다.
  • show-sql : Hibernate가 실행하는 SQL 쿼리를 콘솔에 출력하도록 한다.
  • format-sql : SQL 쿼리의 포맷을 예쁘게 만들어서 출력하도록 설정한다.

spring.jpa.hibernate.ddl-auto 규칙

더보기
  • none : 엔티티가 변경되더라도 데이터베이스를 변경하지 않는다.
  • update : 엔티티의 변경된 부분만 데이터베이스에 적용한다.
  • validate : 엔티티와 테이블 간에 차이점이 있는지 검사만 한다.
  • create : 스프링 부트 서버를 시작할 때 테이블을 모두 삭제한 후 다시 생성한다.
  • create-drop : create와 동일하지만 스프링 부트 서버를 종료할 때에도 테이블을 모두 삭제한다.

1-3. 로깅 구성

build.gradle

logging:
  level:
    org.hibernate.SQL: debug
    org.hibernate.type.descriptor.sql: trace
  • org.hibernate.SQL : debug : Hibernate가 실행하는 실제 SQL 쿼리를 기록한다(디버깅에 도움이 된다.).
  • org.hibernate.type.descriptor.sql: trace : Hibernate가 엔티티 속성을 SQL에 매핑하는 방법에 대한 자세한 정보를 기록한다.

2. 엔티티로 테이블 매핑하기

2-1. 엔티티 속성 구성하기(User.java Gathering.java)

User.java

  • @NoArgsConstructor(access = AccessLevel.PROTECTED) : 객체의 기본생성자를 자동으로 생성한다.
public User() {
}
  • @Id @GenerateValue(strategy = GenerationType.IDENTITY)
  • @Column(nullable = false, unique = true)
  • @CreatedDate
  • @LastModifiedDate

@NoArgsConstructor(access = AccessLevel.PROTECTED)

왜 PROTECTED를 사용해야 하는가?

더보기

1. 프록시 객체 생성을 위해서(지연로딩 할 때)

2. 무분별한 객체 생성에 대해 한 번 더 체크할 수 있는 수단이 되기 때문에

https://docs.jboss.org/hibernate/orm/3.5/reference/en/html/persistent-classes.html#persistent-classes-pojo-constructor

 

Chapter 4. Persistent Classes

Persistent classes are classes in an application that implement the entities of the business problem (e.g. Customer and Order in an E-commerce application). Not all instances of a persistent class are considered to be in the persistent state. For example,

docs.jboss.org

4.1.1. Implement a no-argument constructor

Cat has a no-argument constructor. All persistent classes must have a default constructor (which can be non-public) so that Hibernate can instantiate them using Constructor.newInstance(). It is recommended that you have a default constructor with at least package visibility for runtime proxy generation in Hibernate.

@Builder()와 함께 사용하는 법

 

User.java

import java.time.LocalDateTime;

import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class User {

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

    @Column(nullable = false, unique = true)
    private String email;

    @Column(nullable = false)
    private String password;

    @Column(nullable = false)
    private String name;

    @Column(nullable = false)
    private String companyName;

    private String image;

    @CreatedDate
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime updatedAt;

    private LocalDateTime deletedAt;

}

 

Gathering.java

import java.time.LocalDateTime;

import org.springframework.data.annotation.CreatedBy;

import com.fesi.mukitlist.api.service.request.GatheringServiceCreateRequest;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Gathering {

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

    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private GatheringType type;

    private String name;

    @Column(nullable = false)
    private LocalDateTime dateTime;

    @Column(nullable = false)
    private LocalDateTime registrationEnd;

    @Column(nullable = false)
    private String location;

    @Column(nullable = false)
    private int participantCount = 0;

    @Column(nullable = false)
    private int capacity;

    @CreatedBy
    private String createdBy;

    private LocalDateTime canceledAt;

    @Builder
    private Gathering(GatheringType type, String name, LocalDateTime dateTime, LocalDateTime registrationEnd,
                      String location, int participantCount, int capacity, String createdBy, LocalDateTime canceledAt) {
        this.type = type;
        this.name = name;
        this.dateTime = dateTime;
        this.registrationEnd = registrationEnd;
        this.location = location;
        this.participantCount = participantCount;
        this.capacity = capacity;
        this.createdBy = createdBy;
        this.canceledAt = canceledAt;
    }

    public static Gathering create(GatheringServiceCreateRequest request) {
        return Gathering.builder()
                .location(request.location())
                .type(request.type())
                .name(request.name())
                .dateTime(request.dateTime())
                .capacity(request.capacity())
                .registrationEnd(request.registrationEnd())
                //.createdBy()
                .build();
    }

    public void updateCanceledAt(LocalDateTime canceledTime) {
        this.canceledAt = canceledTime;
    }

    public void joinParticipant() {
        this.participantCount++;
    }

    public void leaveParticipant() {
        this.participantCount--;
    }
    @CreatedDate
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime updatedAt;

 

   [ 세팅 ]

  • @EntityListerners(AuditingEntityListener.class) 어노테이션이 필요하다.
  • 엔티티를 DB에 적용하기 이전에 콜백을 요청할 수 있는 어노테이션이다.

   [ 설명 ]

  • @createdDate : 엔티티 생성 시 특정 필드를 자동으로 데이터베이스에 매핑해주기 위해 사용한다.
  • @LastModifiedDate : 엔티티 최종 수정 날짜를 자동으로 데이터베이스에 매핑해주기 위해 사용한다.

IDENTITY 전략(영속성 컨텍스트)

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)

 

기본 키 생성을 데이터베이스에 위임한다.

3. 리포지터리로 데이터베이스 관리하기

3-1. 리포지터리 생성하기

GatheringRepository.java

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

import com.fesi.mukitlist.api.domain.Gathering;

public interface GatheringRepository extends JpaRepository<Gathering, Long>, JpaSpecificationExecutor<Gathering> {

}

 

  • GatheringRepository 인터페이스를 리포지터리로 만들기 위해 JpaRepository 인터페이스를 상속한다.
  • JpaRepository는 JPA가 제공하는 인터페이스 중 하나로 CRUD 작업을 처리하는 메서드들을 이미 내장하고 있어 데이터 관리 작업을 좀 더 편리하게 처리할 수 있다.

3-2. 테스트 코드 작성하기

MukilistApplicationTests.java

import com.fesi.mukitlist.api.domain.User;
import com.fesi.mukitlist.api.repository.UserRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
public class MukitlistApplicationTests {
class MukitlistApplicationTests {
	@Autowired
	private UserRepository userRepository;

	@Test
	void contextLoads() {
	}
	void testCreateUser() {
		// Create a new user
		User user = User.builder()
				.email("testuser@example.com")
				.password("password123")
				.name("Test User")
				.companyName("Test Company")
				.image("default.jpg")
				.build();

}
		User savedUser = userRepository.save(user);
		System.out.println("Saved User: " + savedUser);

		assertNotNull(savedUser.getId(), "사용자 ID는 저장 후 null이 아니어야 합니다.");
		assertNotNull(savedUser.getCreatedAt(), "CreatedAt은 자동으로 설정되어야 합니다.");
		assertNotNull(savedUser.getUpdatedAt(), "UpdatedAt은 자동으로 설정되어야 합니다.");
		assertNull(savedUser.getDeletedAt(), "DeletedAt은 처음에 null이어야 합니다.");
	}
}

 

 

application.yml

spring:
  h2:
    console:
      enabled: true
      path: /h2-console

  datasource:
    url: jdbc:h2:file:~/mukitListApplication
    driverClassName: org.h2.Driver
    username: sa
    password: ''

  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        format-sql: true