🧩 들어가며
현대의 자바 기반 애플리케이션에서 데이터 접근 계층은 단순한 SQL 실행 그 이상을 요구받고 있습니다.
객체 지향 언어인 Java와 관계형 데이터베이스(RDB) 사이의 간극을 효율적으로 연결해주는 기술이 바로 JPA(Java Persistence API) 입니다.
이번 글에서는 실무 관점에서 JPA가 왜 필요한지, 어떤 구조로 동작하는지, 그리고 실질적으로 우리가 어떻게 다뤄야 하는지에 대해 깊이 있는 시선으로 정리해보려 합니다.
🔍 JPA란 무엇인가?
JPA는 Java 진영의 ORM(Object Relational Mapping) 표준 명세입니다.
즉, 자바 객체 ↔ 관계형 데이터베이스 사이의 매핑을 선언적으로 처리할 수 있도록 도와주는 인터페이스 모음이죠.
💡 Hibernate, EclipseLink, OpenJPA는 JPA의 구현체입니다.
스프링부트에서 주로 사용하는 건 Hibernate이며, JPA를 추상 레이어로 사용합니다.
📐 왜 JPA인가?
1. SQL 중심 개발의 한계
- 중복되는 SQL
- 비즈니스 로직에 쿼리 로직이 섞임
- 테이블 구조 변경 시 코드 유지보수 어려움
- 객체와 테이블 간의 불일치
2. 객체 지향적으로 설계된 애플리케이션의 요구
- 컬렉션 기반의 연관 관계 관리
- 캡슐화된 비즈니스 메서드
- 영속성 생명주기와 트랜잭션 추적
👉 이를 해결하기 위해 JPA는 다음과 같은 추상화를 제공합니다.
🧱 핵심 개념 정리
🗃️ Entity
@Entity
@Table(name = "users")
public class User {
@Id @GeneratedValue
private Long id;
private String name;
@Column(unique = true)
private String email;
}
- 클래스 자체가 데이터베이스 테이블과 매핑됩니다.
- @Id, @Column, @Table 등은 모두 매핑 메타데이터입니다.
🔄 EntityManager
JPA의 핵심 동작을 담당하는 인터페이스.
Hibernate의 Session과 유사한 개념으로, 영속성 컨텍스트를 관리합니다.
EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
User user = em.find(User.class, 1L);
user.setName("변경된 이름");
em.getTransaction().commit();
📌 주의: Spring Data JPA에서는 직접 EntityManager를 다룰 일은 거의 없습니다. 대신, Repository가 이를 추상화합니다.
📦 영속성 컨텍스트 (Persistence Context)
- 엔티티 객체를 1차 캐시에 보관
- 같은 트랜잭션 내 동일 객체 반환 (== 동일성 보장)
- 변경 감지(Dirty Checking) → flush 시 자동 update
User user = em.find(User.class, 1L);
user.setName("홍길동"); // 별도 update 쿼리 없이도 자동 감지됨
🧠 Dirty Checking & Flush
- Dirty Checking: 엔티티 객체의 변경 여부 추적
- Flush: 변경된 내용을 SQL로 동기화 (트랜잭션 커밋 시 자동 수행)
em.flush(); // DB에 SQL 전송
🏗️ Spring Data JPA와의 관계
Spring Data JPA는 JPA의 Repository 패턴을 자동화한 구현체입니다.
즉, JpaRepository 인터페이스만 상속받으면 기본적인 CRUD는 따로 SQL 없이 구현됩니다.
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
}
🚀 커스텀 쿼리는 @Query 어노테이션으로 처리하거나, QueryDSL, Specification으로 확장 가능
🧬 연관관계 매핑
예시: 회원과 주문 (1:N)
@Entity
public class Member {
@Id
private Long id;
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<>();
}
@Entity
public class Order {
@Id
private Long id;
@ManyToOne
private Member member;
}
- 양방향 매핑 시 mappedBy 설정 필수
- 지연 로딩(LAZY)이 기본값이므로 주의
⚠️ 실무에서 자주 하는 실수
항목 | 설명 |
양방향 매핑 남용 | 단방향으로도 충분한 경우가 많음 |
즉시 로딩(EAGER) 사용 | 성능 저하, N+1 문제 발생 가능 |
식별자 전략 잘못 사용 | GenerationType.IDENTITY는 제약이 있음 |
트랜잭션 없이 EntityManager 사용 | 변경 감지 미동작 |
🎯 마무리
JPA는 단순히 SQL을 안 쓰기 위한 도구가 아닙니다.
도메인 모델을 중심으로 한 객체 지향 아키텍처를 구현하기 위한 전략이며,
이는 결국 유지보수성과 생산성을 극대화합니다.
Spring Boot 3와 함께 JPA를 적절히 활용하면,
비즈니스 로직에 집중할 수 있는 유연하고 강력한 백엔드 아키텍처를 구성할 수 있습니다.
'Spring Boot' 카테고리의 다른 글
Spring Boot 3의 Repository, Service, Controller 흐름 (3) | 2025.07.19 |
---|---|
JWT (0) | 2024.01.26 |
스프링 시큐리티 (0) | 2024.01.26 |
front + backend 게시판 CRUD 구현 (1) (0) | 2023.10.19 |