03. 프로젝트 구성
- -
이번 시간에는 SpringBoot에서 Spring Data JPA를 사용하기 위한 프로젝트의 기본 환경 설정에 대해 살펴보자.
프로젝트 개요
게시판을 위한 Spring Data Jpa 프로젝트
우리가 만들어볼 애플리케이션은 회원제 게시판이다. 회원은 여러 개의 게시글을 작성할 수 있고 각각의 게시글은 하나의 첨부 파일과 여러 개의 댓글을 가질 수 있다. 데이터를 저장할 데이터베이스로는 H2를 사용한다.
Spring Data JPA를 통해 접근한다. Spring Data JPA는 JPA 사용하는데 그 구현체로는 Hibernate를 사용한다. 구현체는 EclipseLink등으로 교체할 수도 있다.
Model을 구성하는 Service와 Repository는 빈으로 관리되며 DB의 테이블을 객체화 하기 위한 Entity들이 필요하다.
이번 과정에서 살펴볼 내용은 어떻게 Entity들을 만들고 이 Entity들을 이용해서 Database에 질의하며 비즈니스 로직을 처리하는지 살펴보는 것이다. Database에 질의하는 역할은 Repository가 담당하고 비지니스 로직을 처리하는 역할은 Service가 담당한다.
H2 Database
H2 database 소개
H2 database는 H2는 자바로 작성된 관계형 데이터베이스 관리 시스템으로 아주 가볍고 빠르기 때문에 주로 테스트 용도로 많이 사용된다. 어차피 Java는 JDBC 기반으로 DB를 사용하기 때문에 나중에 운영 시점에 H2를 Oracle이나 MySql로만 바꾸면 전혀 문제는 없다. 즉 profile로 구분해서 dev profile에서는 h2를 사용하고 기본 profile에서는 MySql을 사용하면 그냥 된다.
https://www.h2database.com/html/main.html
H2 Database의 모드
H2 Database는 크게 3가지 모드로 사용할 수 있다. 각 모드의 특징과 JDBC URL을 살펴보자.
모드 | 설명 |
Server Mode |
공홈에서 H2 서버 파일을 다운받아 설치하고 사용하는 버전으로 Server를 띄워놓고 사용하는 방식 - 초기 DB 생성에 문제가 있을 경우 DB 파일 삭제 후 H2 Console에서 Embedded Mode로 생성 후 사용 - JDBC_URL: jdbc:h2:tcp://localhost/~/[db-name] |
Embedded Mode |
H2 서버를 별도로 설치/실행하지 않고 pom.xml에 의존성을 포함한 후 애플리케이션 실행 시 내부적으로 서버를 실행하는 방식 - JDBC_URL: jdbc:h2:~/[db-name] |
In-Memory Mode |
Embedded Mode와 동일하나 데이터를 데이터베이스 파일에 저장하지 않고 메모리에 쓰는 방식 - 단위테스트 처럼 1회성으로 테스트할 때 - JDBC_URL: jdbc:h2:mem:[db-name] |
실제 운영 환경은 Server Mode가 적합하겠지만 예제에서는 Embedded Mode를 사용하고 단위테스트를 진행할 때에는 In-Memory Mode를 사용하자.
프로젝트 구성
생성
기본적인 프로젝트의 구조는 다음과 같이 만들어보자.
- JDK Version: 17
- SpringBoot Version: 3.3.x
- 필요한 의존성
- spring-boot-devtools
- lombok
- h2(주로 사용할 DB), mysql(profile 설정 연습용)
- spring-boot-starter-data-jpa
위와 같이 프로젝트를 생성하고 실행시켜보자.
애플리케이션 실행 후 별 오류 없이 종료되면 일단 성공이고 중간에 HikariCP이라는 Connection Pool이 사용되는 것 정도 살펴두면 좋다. 그냥 "H2를 사용하겠다"는 의도만 밝혔는데 Connection Pool까지 연결해서 환경을 구성하는 것이 Spring Boot가 제시하는 자동 환경 설정의 힘이다!
Connection Pool의 개념은 다음을 참조하자.
https://goodteacher.tistory.com/740
application.properties
이번 예제는 크게 default 까지 크게 4개의 profile을 사용한다.
다음은 default profile에 대한 내용이다.
logging:
level:
root: info
pattern:
console: "%clr(%d{HH:mm:ss} [%-5p] [%c{20}.%M.%L] %m%n)"
spring:
output:
ansi:
enabled: always
jpa:
hibernate:
ddl-auto: validate # 실행 시 테이블 자동 생성 설정
properties:
hibernate:
"[format_sql]": true # 출력되는 sql을 보기 좋게 format 할 것인가?
logging 부분은 기존과 동일하고 jpa와 관련된 속성이 2가지 추가되었다. 이 부분은 나중에 살펴보자.
다음은 h2 및 mysql profile에 대한 설정으로 각각 어떤 DB에 어떤 계정으로 접속할 것인지가 담긴다. h2에서는 sql을 실행할 웹 콘솔을 어떻게 접근할 것인지에 대한 설정이 추가 되었다.
---
spring:
config:
activate:
on-profile:
- h2
h2:
console:
enabled: true # 웹 console을 통해 h2 db에 접속할 것인가?
path: /h2-console # 웹 console의 접속 경로
datasource:
url: jdbc:h2:~/spring_board
driver-class-name: org.h2.Driver
username: doding
password: 1234
---
spring:
config:
activate:
on-profile:
- mysql
datasource:
url: jdbc:mysql://localhost:3306/spring_board
driver-class-name: com.mysql.cj.jdbc.Driver
username: quietjun
password: quietjun
설정 확인
이제 설정이 잘 되었는지 간단히 테스트해 보자. 지금은 default profile과 h2 profile을 사용하고 있다.
@DataJpaTest를 이용한 단위테스트 과정에서는 기본적으로 database는 in-memory 기반 db로, datasource는 EmbeddedDataSourceProxy로 변경해서 테스트를 진행한다. 원하는 DB로 고정하려면 @AutoConfigureTestDatabase 설정이 필요하다.
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@DataJpaTest // 기본 설정은 테스트 용으로 h2를 in-memory 기반으로 사용하려 함
@ActiveProfiles({"h2"}) // 사용할 profile 설정
@AutoConfigureTestDatabase(replace=AutoConfigureTestDatabase.Replace.NONE)// 기본 DB 그대로 사용
@Slf4j
class BoardappApplicationTests {
@Autowired
DataSource ds;
@Test
void contextLoads() throws SQLException {
log.info("cp type: {}", ds.getClass().getSimpleName());
log.info("url : {}", ds.getConnection().getMetaData().getURL());
if (ds instanceof HikariDataSource hds) {
log.info("max pool size: {}", hds.getMaximumPoolSize());
log.info("min idle cnt: {}", hds.getMinimumIdle());
log.info("idle time: {}", hds.getIdleTimeout());
}
}
}
로그를 확인해 보면 SpringBoot는 기본적으로 Hikari DataSource를 이용하고 최소 connection 개수를 10개로 사용하고 있다.
17:23:29 [INFO ] [c.d.b.BoardappApplicationTests.logStartupProfileInfo.660] The following 1 profile is active: "h2"
17:23:31 [INFO ] [c.d.b.BoardappApplicationTests.contextLoads.24] cp type: HikariDataSource
17:23:31 [INFO ] [c.d.b.BoardappApplicationTests.contextLoads.25] url: jdbc:h2:~/spring_board
17:23:31 [INFO ] [c.d.b.BoardappApplicationTests.contextLoads.28] max pool size: 10
17:23:31 [INFO ] [c.d.b.BoardappApplicationTests.contextLoads.29] min idle cnt: 10
17:23:31 [INFO ] [c.d.b.BoardappApplicationTests.contextLoads.30] idle time: 600000
이번에는 mysql profile을 이용해보자. 만약 MySql이 설치되지 않은 경우 mysql profile은 당연히 오류를 내겠지만 profile만 잘 이해하면 굳이 설치해서 테스트 해보지 않아도 괜찮다.
17:19:26 [INFO ] [c.d.b.BoardappApplicationTests.logStartupProfileInfo.660] The following 1 profile is active: "mysql"
17:19:28 [INFO ] [c.d.b.BoardappApplicationTests.contextLoads.24] cp type: HikariDataSource
17:19:28 [INFO ] [c.d.b.BoardappApplicationTests.contextLoads.25] url: jdbc:mysql://localhost:3306/spring_board?serverTimezone=UTC
17:19:28 [INFO ] [c.d.b.BoardappApplicationTests.contextLoads.28] max pool size: 10
17:19:28 [INFO ] [c.d.b.BoardappApplicationTests.contextLoads.29] min idle cnt: 10
17:19:28 [INFO ] [c.d.b.BoardappApplicationTests.contextLoads.30] idle time: 600000
이처럼 profile만 변경하면 어렵지 않게 해당 profile에 적합한 환경으로 프로젝트를 진행할 수 있다. 앞으로는 기본적으로 h2 profile을 이용해서 과정을 진행해보자.
dev profile 구성
다음은 개발 과정에서 사용할 dev profile에 대한 내용이다. 대부분 내용에 대한 해석은 주석으로 갈음할 수 있을 것 같다.
---
spring:
config:
activate:
on-profile:
- dev
jpa:
hibernate:
ddl-auto: create # 실행 시 테이블 자동 생성
logging:
level:
"[com.doding]": trace
"[org.hibernate.orm.jdbc.bind]": trace # 실행되는 sql에 전달되는 파라미터 출력
이제 테스트 파일에서 active profile을 dev로 설정하고 @AutoConfigureTestDatabase를 주석후 테스트를 실행해보자.
@DataJpaTest
@ActiveProfiles({"dev"}) // 사용할 profile 설정
// @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Slf4j
class BoardappApplicationTests { ...}
실행 결과에서 dev profile이 적용되는지 실제 동작하는 cp 타입이 EmbeddedDataSourceProxy인지, data source url이 jdbc:h2:mem으로 시작하는지 살펴보자.
17:39:41 [INFO ] [c.d.b.BoardappApplicationTests.logStartupProfileInfo.660] The following 1 profile is active: "dev"
17:39:43 [INFO ] [c.d.b.BoardappApplicationTests.contextLoads.24] cp type: EmbeddedDataSourceProxy
17:39:43 [INFO ] [c.d.b.BoardappApplicationTests.contextLoads.25] url: jdbc:h2:mem:d7df918f-8033-4d16-a71f-256f68c198c6
DB 스키마 자동 생성
앞서 설정에서 살펴봤던 spring.jpa.hibernate.ddl-auto에 의한 DDL 관리에 대해 좀 더 알아보자.
spring.jpa.hibernate.ddl-auto
spring.jpa.hibernate.ddl-auto 말 그대로 Entity에 대한 ddl을 자동으로 생성하는 것에 대한 속성으로 create, create-drop, update, validate, none 5가지를 사용할 수 있다.
value 값 | 설명 |
create | 기존 테이블을 삭제하고 새로 생성(시작 -> DROP -> CREATE) |
create-drop | create에 애플리케이션 종료 시 생성한 DDL 제거 기능(시작 -> DROP -> CREATE -> 종료 -> DROP) |
update | 테이블과 엔티티 매핑 정보를 비교해서 변경 사항만 수정(사용되지 않는 테이블, 컬럼은 삭제하지 않음) |
validate | 테이블과 매핑 정보가 다르면 경고를 남기고 애플리케이션 중지 |
none | 일반 환경에서 기본 값으로 자동 생성을 사용하지 않음 |
Entity를 위한 클래스 생성
그럼 Entity 작성법에 대한 설명은 뒤로 미루고 일단 Entity로 사용하기 위한 Member를 작성하고 기존의 단위테스트를 실행해보자.
package com.doding.boardapp.member.entity
@Getter
@Setter
@Entity
public class Member {
@Id
private String id;
private String name;
private String pass;
}
DDL 자동 생성의 달콤함
테이블을 자동 생성하게 되면 개발자가 테이블을 직접 생성하지 않아도 되기 때문에 매우 편리하다. JPA는 매핑 내용을 바탕으로 설정에 지정한 DB에 적합한 DDL을 만들어준다. 데이터베이스 마다 컬럼 타입들이 조금 씩 다를 수 있는데 이를 DB Dialect(DB 방언)이라고 한다. JPA는 거의 대부분의 DB에 대해 준비하고 있어서 연결된 DB에 적합한 SQL을 생성해준다. 다음은 각각 H2와 MySql를 사용했을 때 생성되는 DDL이다.
확인을 위해서 @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) 가 필요하다.
# H2 사용 시 생성된 쿼리: profile: h2, dev
Hibernate:
drop table if exists member cascade
Hibernate:
create table member (
id varchar(255) not null,
name varchar(255),
pass varchar(255),
primary key (id)
)
# mysql 사용 시 생성된 쿼리: profile: mysql, dev
Hibernate:
drop table if exists member
Hibernate:
create table member (
id varchar(255) not null,
name varchar(255),
pass varchar(255),
primary key (id))
engine=InnoDB
따라서 프로젝트 진행 과정에 데이터베이스를 H2에서 MySql로 변경한다고 하더라도 코드적으로 변경해야 할 부분은 거의 없다.
모든 분야가 마찬가지겠지만 자동 생성의 치명적인 문제점은 일반적이라는 점이다. 대부분은 잘 동작하지만 최적화 등에서 문제를 발생시킬 수 있다. 자동 생성되는 DDL도 마찬가지이다. JPA에서 생성되는 쿼리들은 100% 신뢰하지 말고 반드시 꼭 검증해봐야 한다.
언제 누구를 사용해야 할까?
상황에 따라 다르겠지만 일반적으로 개발 초기 단계에서는 create나 update를 주로 사용한다. 이 시기에는 프로토타입을 잡는 시기 이기 때문에 아직 DB 구조가 명확하지 않고 변경이 잦은 경우가 많기 때문이다. 편리하고 신기하지만 매우 위험하기도 하다.
하지만 개발이 어느 정도 성숙기에 이르면 스키마가 완성되기 때문에 DDL을 건드리지 않는 validate, none이 주로 사용된다. 운용 시에는 당연히 create 등 DDL이 변경되는 설정은 절대로 사용하면 안 되고 validate나 none을 사용하는 것을 권장한다.
JPA를 만나본 첫 느낌은 무언가 신기하면서 낮설다. 다음 포스트에서 본격적으로 Spring Data JPA를 학습해보자.
'Spring Model > 01. Model' 카테고리의 다른 글
@Repository의 역할 (0) | 2024.03.22 |
---|---|
Connection Pool (0) | 2024.03.22 |
Entity vs VO vs DTO (0) | 2023.10.19 |
02. Spring Data와 Spring Data JPA (0) | 2020.06.22 |
01. Model 영역과 Spring (0) | 2019.09.09 |
소중한 공감 감사합니다