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. 무분별한 객체 생성에 대해 한 번 더 체크할 수 있는 수단이 되기 때문에
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
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