본문 바로가기
Kotlin Spring/Kotlin Spring 강의 내용

[부록] JPA(Java Persistence API)

by Bill Lab 2026. 1. 27.
728x90

1. JPA(Java Persistence API) 란?

     - 자바 진영에서 사용하는 ORM(Object-Relational Mapping) 기술 표준

     - 관계형 데이터베이스(RDB)를 자바 객체로 다루기 위한 인터페이스의 집합

 

2. ORM(Object-Relational Mapping) 이란?

     - 객체(Object)와 관계형 데이터베이스(RDB)의 테이블을 매핑하는 기술

     - 애플리케이션의 객체를 RDB 테이블에 자동으로 영속화(persistence) 해주는 기술

     - 개발자는 객체만 다루고, 실제 SQL 생성과 실행은 ORM 프레임워크가 담당

 

3. ORM 장점

     1) SQL이 아닌 객체 중심 개발

          - SQL 대신 메서드 호출로 DB 조작

          - 비즈니스 로직에 집중 가능

     2) 반복 코드 제거

          - CRUD SQL 작성 불필요

          - 선언문, 매핑 코드 감소

     3) 객체지향적인 설계 가능

          - 상속, 연관관계, 캡슐화 등 OOP 개념 그대로 사용

          - 생산성 증가

     4) 유지보수와 리팩토링에 유리

          - 매핑 정보가 클래스에 명시됨

          - ERD 의존도 감소

     5) DB 벤더 종속성 감소

          - 타 db 변경 시(예, MySQL → PostgreSQL 변경)

          - SQL 수정 없이 설정 변경만으로 대응 가능

 

4. JPA 의 단점

     1) 잘못된 설계 시 성능 문제

         : 대규모/복잡한 도메인에서 N+1 문제, 불필요한 조인 발생 가능

     2) 복잡한 쿼리의 한계

         : 통계성, 리포트성 쿼리는 튜닝 필요(결국 Native SQL 사용 가능성 존재)

     3) 학습 비용

          - 영속성 컨텍스트

          - 지연 로딩, 즉시 로딩

          - 변경 감지 등 개념 학습 필요

 

5. 왜 JPA를 사용해야 할까?

     : SQL 중심 개발에서 객체 중심 개발로 전환

 

     1) JPA가 제공하는 가치

         - 반복적인 CRUD SQL 자동 처리

         - 객체 관계를 기반으로 SQL 자동 생성

         - 실행될 SQL 예측 가능

 

    2) Native SQL 지원

         - 성능 이슈

         - 복잡한 쿼리 → 필요한 경우 직접 SQL 작성 가능

         - 객체지향과 관계형 db 사이의 패러다임 불일치 이슈 해결

         - 상속, 연관관계(조인) 등을 지원

 

6. 세팅 방법

     - Gradle 의존성 설정 (build.gradle.kts)

plugins {
    id("org.springframework.boot") version "3.2.0"
    id("io.spring.dependency-management") version "1.1.0"
    kotlin("jvm") version "1.9.10"
    kotlin("plugin.spring") version "1.9.10"
    kotlin("plugin.jpa") version "1.9.10"
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.jetbrains.kotlin:kotlin-reflect")

    runtimeOnly("com.mysql:mysql-connector-j") 

    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

 

     - application.yml 설정

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb?serverTimezone=UTC&useSSL=false
    username: root
    password: 1234
    driver-class-name: com.mysql.cj.jdbc.Driver

  jpa:
    hibernate:
      ddl-auto: validate   # create / create-drop / validate / update
    show-sql: true       # SQL 로그 확인
    properties:
      hibernate:
        format_sql: true
        use_sql_comments: true

 

     - Entity

import jakarta.persistence.*

@Entity
@Table(name = "members")
data class Member(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long = 0,

    val username: String,

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "team_id")
    var team: Team? = null
)

@Entity
@Table(name = "teams")
data class Team(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long = 0,

    val name: String
)

 

     - repository

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

interface MemberRepository : JpaRepository<Member, Long> {
    fun findByUsername(username: String): Member?
}

interface TeamRepository : JpaRepository<Team, Long>

 

     - service

import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

@Service
class MemberService(
    private val memberRepository: MemberRepository,
    private val teamRepository: TeamRepository
) {

    @Transactional
    fun createMember(username: String, teamName: String): Member {
        val team = Team(name = teamName)
        teamRepository.save(team)

        val member = Member(username = username, team = team)
        return memberRepository.save(member)
    }

    @Transactional(readOnly = true)
    fun getMember(id: Long): Member? {
        return memberRepository.findById(id).orElse(null)
    }
}
728x90