본문 바로가기
Java/Spring Boot

[SpringBoot] H2database, JPA 설정과 사용법

by curious week 2024. 12. 22.
    @Test
    void testJpa() {
        Optional<Question> oq = this.questionRepository.findById(1);
        if(oq.isPresent()) {
            Question q = oq.get();
            assertEquals("sbb가 무엇인가요?", q.getSubject());
        }
    }

ORM(Object-Relational Mapping):  ORM은 SQL을 사용하지 않고 데이터베이스를 관리할 수 있는 도구, DBMS의 종류에 관계없이 일관된 자바 코드를 사용할 수 있어서 프로그램을 유지·보수하기가 편리하다. 

JPA(Java Persistence API): 하이버네이트는 JPA의 인터페이스를 구현한 실제 클래스이자 자바의 ORM 프레임워크로, 스프링 부트에서 데이터베이스를 관리하기 쉽게 도와준다.


H2 database: 자바 기반의 경량 DBMS이다. H2 데이터베이스를 사용하여 빠르게 개발하고 실제 운영 시스템에는 좀 더 규모 있는 DBMS를 사용하는 것이 일반적이다.

// build.gradle
dependencies { 
  runtimeOnly 'com.h2database:h2'
}

build.gradle에 runtimeOnly 'com.h2database:h2' 추가

// application.properties
# DATABASE
# 콘솔에 접속할 지 여부
spring.h2.console.enabled=true
# 콘솔 접속하기 위한 url 경로
spring.h2.console.path=/h2-console
# 데이터베이스에 접속하기 위한 경로, 홈 디렉터리에 local.mv.db 생성
spring.datasource.url=jdbc:h2:~/local
# 데이터베이스에 접속할 때 사용하는 드라이버 클래스명
spring.datasource.driverClassName=org.h2.Driver
# 데이터베이스 사용자 명
spring.datasource.username=sa
# 데이터베이스 비밀번호
spring.datasource.password=

http://localhost:8080/h2-console로 접속해서 확인할 수 있다.

h2 DB에 접속이 안된다면, H2 console available at '/h2-console'. Database available at 'jdbc:h2:~/local'에서 보듯 접속 url이 일치하는지 확인해 본다.


JPA 사용하기

// build.gradle
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
}
// application.properties
# JPA
# spring.jpa.properties.hibernate.dialect 스프링 부트와 하이버네이트를 함께 사용할 때 필요한 설정 항목
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
# 엔티티를 기준으로 데이터의 테이블을 생성하는 규칙을 설정
spring.jpa.hibernate.ddl-auto=update

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

spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.show_sql=true
# 콘솔 로그에서 데이터베이스에서 실행된 쿼리문를 확인할 수 있다.

package com.mysite.sbb;

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

public interface QuestionRepository extends JpaRepository<Question, Integer> {

}

JpaRepository는 JPA가 제공하는 인터페이스 중 하나로 CRUD 작업을 처리하는 메서드들을 이미 내장하고 있어 데이터 관리 작업을 좀 더 편리하게 처리할 수 있다.


JUnit을 사용하면 이러한 프로세스를 따르지 않아도 리포지터리만 개별적으로 실행해 테스트해 볼 수 있다. 

// build.gradle
dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter' 
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

package com.mysite.sbb;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.List;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;


@SpringBootTest
class SbbApplicationTests {

    @Autowired
    private QuestionRepository questionRepository;

    @Test
    void testJpa() {
        List<Question> all = this.questionRepository.findAll();
        assertEquals(2, all.size());

        Question q = all.get(0);
        assertEquals("sbb가 무엇인가요?", q.getSubject());
    }
}

findAll: (=SELECT * FROM QUESTION) 해당 엔티티의 모든 데이터를 조회하는 메서드

assertEquals(기댓값, 실젯값): 기댓값과 실제값이 동일한지를 조사, 기댓값과 실젯값이 동일하지 않다면 테스트는 실패로 처리

Optional<Question> question = questionRepository.findById(1);
if (question.isPresent()) {
    Question q = question.get();
    // q를 이용한 작업
} else {
    // 값이 없을 경우 처리
}

findById(ID id): findBy 뒤에 필드 이름을 적으면 해당 필드로 검색. 반환값은 Optional<T>이므로, 값이 있을 수도 있고 없을 수도 있기 때문에 isPresent() 또는 ifPresent() 등을 사용하여 값을 확인하고 처리해야 한다.

호출한 값이 존재할 수도 있고, 존재하지 않을 수도 있을 때, 리턴 타입으로 Optional이 사용

Question newQuestion = new Question("질문 제목", "질문 내용");
questionRepository.save(newQuestion);  // 새 질문을 저장

save(): 엔티티 객체를 데이터베이스에 저장하거나, 이미 존재하는 객체는 갱신(Update)한다. id 값이 없는 새 객체는 삽입되고, id가 있는 객체는 업데이트된다.

questionRepository.deleteById(1);  // id가 1인 질문을 삭제

deleteById(ID id): 특정 id를 가진 데이터를 삭제. void 타입이며, 주어진 id에 해당하는 데이터가 없으면 아무 작업도 하지 않는다.

questionRepository.deleteAll();  // 모든 질문을 삭제

deleteAll(): 엔티티 테이블의 모든 데이터를 삭제

long count = questionRepository.count();  // 테이블에 있는 질문의 수를 반환

count(): 해당 엔티티 테이블에 저장된 데이터의 개수를 반환

boolean exists = questionRepository.existsById(1);  // id가 1인 질문이 존재하는지 확인

existsById: 특정 id를 가진 데이터가 존재하는지 여부를 확인. 반환값은 boolean이며, 해당 id에 해당하는 데이터가 존재하면 true, 아니면 false를 반환

Sort sort = Sort.by(Sort.Order.asc("subject"));  // subject 컬럼을 기준으로 오름차순 정렬
List<Question> sortedQuestions = questionRepository.findAll(sort);

findAll(Sort sort): 이터를 특정 기준으로 정렬해서 조회. Sort 객체를 인자로 받아, 어떤 컬럼을 기준으로 오름차순/내림차순 정렬할지 지정할 수 있다.

Pageable pageable = PageRequest.of(0, 10, Sort.by("subject").ascending());  // 첫 번째 페이지, 10개 항목
Page<Question> questionsPage = questionRepository.findAll(pageable);
List<Question> questions = questionsPage.getContent();  // 페이징된 질문 리스트

findAll(Pageable pageable): 페이징 처리가 가능한 데이터 조회를 제공한다. Pageable 객체를 사용하여 페이지 번호, 페이지 크기 등을 지정할 수 있다.

List<Question> topQuestions = questionRepository.findTop5ByOrderByCreateDateDesc();

findTopN(): 조건에 맞는 데이터 중 상위 N개의 데이터를 조회


SQL연산자

List<Question> findBySubjectAndContent(String subject, String content);

And: `subject` `content` 열이 각각 일치하는 데이터를 조회한다.

List<Question> findBySubjectOrContent(String subject, String content);

Or: `subject` 열 또는 `content` 열이 일치하는 데이터를 조회한다.

List<Question> findByCreateDateBetween(LocalDateTime fromDate, LocalDateTime toDate);

Between: `createDate` 열에서 주어진 범위 내에 있는 데이터를 조회한다.

List<Question> findByIdLessThan(Integer id);

LessThan: `id` 열에서 조건보다 작은 데이터를 조회한다.

List<Question> findByIdGreaterThanEqual(Integer id);

GreaterThanEqual: `id` 열에서 조건보다 크거나 같은 데이터를 조회한다.

List<Question> findBySubjectLike(String subject);

Like: `subject` 열에서 문자열 `subject`를 포함한 데이터를 조회한다.

XX%: 'XX'로 시작하는 문자열, %XX: 'XX'로 끝나는 문자열, %XX%: 'XX'를 포함하는 문자열

List<Question> findBySubjectIn(String[] subjects);

In: `subject` 열의 데이터가 주어진 배열에 포함되는 데이터를 조회한다.

List<Question> findBySubjectOrderByCreateDateAsc(String subject);

OrderBy___Asc: `createDate` 열을 오름차순으로 정렬하여 반환한다.

 


H2 DB 오류

Database may be already in use: "/Users/sisyphusdb.mv.db"

에러 원인

이 에러는 H2 데이터베이스 파일 기반 모드(file-based mode) 를 사용할 때 흔히 발생하는 문제로,
이미 해당 데이터베이스 파일을 사용하는 다른 프로세스나 애플리케이션이 존재하기 때문입니다.

즉, 같은 DB파일을 두 번 동시에 접근하려고 할 때 발생합니다.


해결 방법

아래 방법 중 하나를 선택해 해결하면 됩니다.

방법: 기존 프로세스 종료

이미 실행 중인 Spring Boot 애플리케이션 또는 H2 콘솔 서버가 있다면 종료하세요.

  • IntelliJ / Eclipse 등에서 이전 애플리케이션을 중지 후 다시 실행.
  • 백그라운드 프로세스에서 실행 중이라면 강제 종료.

Mac의 경우 터미널에서 아래와 같이 프로세스 종료:

lsof | grep sisyphusdb.mv.db

→ 프로세스 번호(PID) 확인 후,

kill -9 {PID}

 

'Java > Spring Boot' 카테고리의 다른 글

[Spring Boot] email 전송하기  (2) 2025.01.29
[SpringBoot] Thymeleaf  (1) 2024.12.25
[SpringBoot] Spring Test  (1) 2024.12.25
[SpringBoot] Annotation  (0) 2024.12.22
[SpringBoot] 기본 파일  (2) 2024.12.22