Spring/Spring Boot

[Spring Boot] 4. Data: SQL Databases

noahkim_ 2023. 10. 13. 17:06

0. SQL Database

Spring Framework는 광범위한 SQL Database 와의 작업을 지원합니다.

  • JdbcTemplate : JDBC 기술을 사용하여 기존의 코드를 단순화화고, 오류 처리 및 리소스 관리를 개선한 헬퍼 클래스
  • Hibernate : JPA 기반 ORM 기술

 

Spring Data JPA는 Repository 인터페이스를 제공합/니다.

  • Repository 인터페이스 구현을 통해 Entity 관련 Table과 직접적으로 통신할 수 있습니다.
  • Repository 인터페이스가 제공하는 메서드 이름의 컨벤션으로 메서드를 생성하면 자동으로 관련 쿼리를 생성하여 통신합니다.

 

1. DataSource

항목 설명
인터페이스
javax.sql.DataSource
기능
Database Connection 생성 및 관리
연결 유형
일반 연결, 풀링된 연결, 분산 연결 등
사용 정보
URL, Credentials (username/password)

 

DataSource Configuration

항목 설명
설정 방식
spring.datasource.* 외부 설정 프로퍼티 사용
Driver 지정
spring.datasource.driver-class-name에 벤더별 FQCN 지정

 

설정) DataSource (application.yml)

더보기
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/springboot_document
    username: root
    password: password
    driver-class-name: com.mysql.jdbc.Driver

 

Supported Connection Pools

HikariCP
항목 설명
고성능
타 커넥션 풀보다 빠르고 효율적 (Tomcat, DBCP 등)
경량화
내부 코드가 심플
최소한의 기능으로 구성됨
빠른 응답 속도
짧은 커넥션 생성 시간
낮은 오버헤드
자동 튜닝
대부분의 설정이 자동 최적화됨
Spring Boot 통합
spring-boot-starter-jdbc 또는 data-jpa 의존성 추가 시, 자동 설정됨

 

설정) HikariCP (application.yml)

더보기
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/springboot_document
    username: root
    password: password
    driver-class-name: com.mysql.jdbc.Driver
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      minimum-idle: 5
      maximum-pool-size: 20
      idle-timeout: 30000
      pool-name: MyHikariCP
      max-lifetime: 1800000
      connection-timeout: 30000
      auto-commit: true

 

설정 항목 한 줄 요약 상세 설명
minimum-idle 최소 유지 커넥션 수
커넥션 풀이 유휴 상태일 때 유지할 최소 커넥션 수입니다.
성능을 위해 일정 수를 유지함.
기본값: 10
maximum-pool-size 최대 커넥션 수
동시에 사용할 수 있는 최대 커넥션 수입니다.
서버 트래픽/성능 요구에 따라 조정.
기본값: 10
idle-timeout 유휴 커넥션 종료 시간
커넥션이 유휴 상태로 유지될 수 있는 최대 시간(ms).
이후 커넥션은 종료됩니다.
기본값: 600000 (10분)
pool-name 커넥션 풀 이름
모니터링/디버깅용 커넥션 풀 이름입니다.
명확한 식별이 가능하게 합니다.
max-lifetime 커넥션의 최대 생존 시간
커넥션이 생성된 이후 유지될 수 있는 최대 시간(ms).
이 시간이 지나면 커넥션은 종료되고 새로 생성됩니다.
기본값: 1800000 (30분)
connection-timeout 커넥션 획득 대기 시간
커넥션 풀이 가득 찼을 때, 커넥션을 얻기 위해 기다릴 수 있는 최대 시간(ms).
초과 시 예외 발생.
기본값: 30000 (30초)
auto-commit 자동 커밋 여부
쿼리 실행 후 자동으로 커밋할지 여부.
일반적으로 true이며, 트랜잭션 제어 시 false로 설정합니다.

 

Embedded Database Support

항목 설명
지원 DB
H2, HSQL, Derby (in-memory)
자동 설정 조건
classpath에 관련 의존성 존재 시
Connection URL 자동 설정됨
선택 옵션
spring.datasource.embedded-database-connection 프로퍼티로 지정 가능

 

2. Using JdbcTemplate

  • Spring Boot는 JdbcTemplate와 NamedParameterJdbcTemplate 클래스를 자동설정합니다.
  • 빈을 주입받아 사용할 수 있습니다.

 

3. JPA and Spring Data JPA

  • JPA는 Java Persistence API의 약자로 Object를 RDB의 테이블과 매핑을 도와주는 자바 핵심 명세입니다.

 

spring-boot-starter-data-jpa

  • spring boot에서 JPA 기반 ORM을 사용하는데 필요한 의존성을 제공하는 모듈
구성요소 설명
Hibernate JPA 표준 명세를 구현하는 라이브러리 (가장 유명함)
Spring ORM
Spring에서 ORM 지원을 위한 모듈
Spring Data JPA
JPA 기반 Repository 구현을 자동화해주는 모듈

 

Entity Scanning

항목 설명
Entity 정의
@Entity, @Embeddable, @MappedSuperclass
Entity 스캔 방식
@SpringBootApplication 아래의 패키지를 자동 스캔
기존 방식
persistence.xml 사용 (Spring Boot에서는 필요 없음)

 

예제) @Entity

더보기
create table user (
    id int auto_increment primary key,
    name varchar(10)
);
@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // AUTO_INCREMENT에 해당
    private int id;
    private String name;

    public User() {}
    public User(String name) { this.name = name; }

    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}

 

JPA 초기화 도구

항목 SmartInitializingSingleton ObjectProvider
설명 모든 빈이 초기화된 후 실행되는 콜백 인터페이스
지연 의존성 주입 도구
목적 모든 빈 초기화 후 후처리
특정 빈의 지연 주입
조건적 사용
실행 시점 모든 싱글턴 빈 초기화 후 1회 실행
필요 시점에 사용자가 직접 호출
예외 상황 모든 빈이 있어야 안전
빈이 없어도 안전하게 처리 가능
대표 사용처 초기 데이터 세팅, JPA 초기화 등
선택적 기능 로딩, 순환 참조 회피 등

 

예제) SmartInitializingSingleton

더보기
@Component
public class DataInitializer implements SmartInitializingSingleton {

    private final UserRepository userRepository;

    public DataInitializer(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public void afterSingletonsInstantiated() {
        // 모든 빈이 초기화된 후 실행
        if (userRepository.count() == 0) {
            userRepository.save(new User("admin", "admin@example.com"));
            System.out.println("초기 admin 계정을 생성했습니다.");
        }
    }
}

 

예제) ObjectProvider

더보기
@Component
public class ReportService {

    private final ObjectProvider<EmailService> emailServiceProvider;

    public ReportService(ObjectProvider<EmailService> emailServiceProvider) {
        this.emailServiceProvider = emailServiceProvider;
    }

    public void sendReport() {
        EmailService emailService = emailServiceProvider.getIfAvailable();
        if (emailService != null) {
            emailService.send("admin@example.com", "Daily Report");
        } else {
            System.out.println("EmailService가 없어 보고서를 전송하지 않습니다.");
        }
    }
}

 

 

Bootstrapping mode

  • Spring Data JPA Repository를 초기화하는 시점을 설정하는 옵션
  • spring.data.jpa.repositories.bootstrap-mode에서 설정합니다.
모드 설명 초기화 시점 비동기 여부 예시
default 기본 동기 초기화 애플리케이션 컨텍스트 초기화 시 바로 Repository 생성됨 대부분의 경우 사용되는 기본값
deferred 비동기 초기화 ApplicationContext 초기화 완료 이후에 백그라운드에서 초기화
부트 시간이 긴 Repository가 많은 경우, 빠른 기동을 원할 때 사용
예: Admin 기능용 Repository만 30개 있어도 사용자 기능과 무관한 경우
lazy 지연 초기화 최초 요청 시점에 초기화
정말 필요한 시점까지 객체 생성/연결 지연
예: 특수 API에서만 쓰이는 Repository를 매번 안 띄우고 싶을 때
  • 비동기 초기화 사용 시 AsyncTaskExecutor가 필요함 (기본 이름은 applicationTaskExecutor)

 

예제) deferred

더보기
spring:
  data:
    jpa:
      repositories:
        bootstrap-mode: deferred
@Repository
public interface UserRepository extends JpaRepository<User, Integer> {

}
public HelloController(ObjectProvider<UserRepository> userRepositoryObjectProvider) {
    this.userRepositoryObjectProvider = userRepositoryObjectProvider;

    System.out.println("✅ HelloController 생성자 호출됨");
}
@Component
public class RepositoryLogger implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (beanName.contains("userRepository")) System.out.println("🟡 Repository 초기화됨: " + beanName + " (" + bean.getClass().getName() + ")");

        return bean;
    }
}
✅ HelloController 생성자 호출됨
🟡 Repository 초기화됨: userRepository (org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean)

 

예제) lazy

더보기
spring:
  data:
    jpa:
      repositories:
        bootstrap-mode: lazy
@Repository
public interface UserRepository extends JpaRepository<User, Integer> {

}
public HelloController(ObjectProvider<UserRepository> userRepositoryObjectProvider) {
    this.userRepositoryObjectProvider = userRepositoryObjectProvider;

    System.out.println("✅ HelloController 생성자 호출됨");
}

@GetMapping("/bootstrap-mode")
public ResponseEntity<Long> bootstrapMode() {
    return ResponseEntity.ok().body(userRepositoryObjectProvider.getObject().findAll().stream().count());
}
@Component
public class RepositoryLogger implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (beanName.contains("userRepository")) System.out.println("🟡 Repository 초기화됨: " + beanName + " (" + bean.getClass().getName() + ")");

        return bean;
    }
}
✅ HelloController 생성자 호출됨
------------------------------------------------------------------------
(/bootstrap-mode 호출 시)
🟡 Repository 초기화됨: userRepository (org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean)

 

Creating and Dropping JPA Databases

설정 설명
spring.jpa.hibernate.ddl-auto
Hibernate의 hbm2ddl.auto 속성
(ex. none, validate, update, create, create-drop)
spring.jpa.generate-ddl
자동 DDL 생성 여부 
- Embedded DB일 경우만 자동으로 생성됨 (기본값: 설정 X)
- ApplicationContext 초기화 완료 시 DDL이 실행됨

 

Open EntityManager in View (OEMIV)

  • HTTP 요청-응답 수명 주기 전체에서 EntityManager를 열어두는 패턴
  • 일반적으로 서비스 계층에서 트랜잭션이 종료되고 데이터베이스 연결이 반환된 후에 EntityManager가 닫히게 됩니다.
    • 이 시점에 지연 로딩을 시도하면 LazyInitializationException이 발생합니다.
항목 설명
목적
View 렌더링 단계까지 EntityManager의 Lazy 로딩을 가능하게 함
장점 HTTP 응답을 생성하는 동안 지연 로딩(Lazy Loading)을 사용할 수 있게 됨
단점
- 필요 없는 쿼리 실행 가능성 있음 → 성능 저하
- 트랜잭션이 닫힌 후 데이터 로딩 → 일관성 문제 발생 가능
기본 동작
OpenEntityManagerInViewInterceptor 자동 등록됨
설정
spring.jpa.open-in-view=true

 

예제) LazyInitializationException 발생

더보기
spring:
  jpa:
    open-in-view: false
@Controller
@RequiredArgsConstructor
public class ViewController {

    private final TeamRepository teamRepository;

    @GetMapping("/teams/{id}")
    public String getTeam(@PathVariable Integer id, Model model) {
        model.addAttribute("team", teamRepository.findById(id).get());
        return "team-view";
    }
}
<!-- src/main/resources/templates/team-view.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <ul>
            <li th:each="member : ${team.members}">
                <span th:text="${member.name}">회원명</span>
            </li>
        </ul>
    </body>
</html>
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: org.example.jpa.Team.members: could not initialize proxy - no Session

참고