JPA가 뭐야?
📌 목차
- JPA를 한 문장으로 설명하면
- 전통적인 방식의 불편함
- ORM이 나온 이유
- JPA는 자바의 ORM 표준이다
- JPA가 해주는 3가지
- 영속성 컨텍스트: JPA의 작업 공간
- 엔티티의 4가지 상태
- JPQL: 객체로 쿼리 작성하기
- 연관관계: 객체끼리 연결하기
- 즉시 로딩 vs 지연 로딩
- JPA를 쓰는 이유
- JPA의 단점
- Spring Data JPA
- 정리
1. JPA를 한 문장으로 설명하면
JPA(Java Persistence API)는 자바 애플리케이션에서 객체 관계 매핑(ORM)을 사용하여 관계형 데이터베이스를 쉽게 다룰 수 있도록 해주는 자바 API다.
JPA를 사용하면 자바 객체와 데이터베이스 테이블 간의 매핑을 정의하고, 데이터베이스의 데이터를 객체 형태로 다룰 수 있게 된다. 이를 통해 객체 지향적인 접근 방식을 유지할 수 있다.
2. 전통적인 방식의 불편함
웹 애플리케이션을 만들면 데이터베이스를 항상 사용한다. 회원 정보를 저장하고, 게시글을 조회하고, 주문 내역을 업데이트하는 모든 작업이 데이터베이스와 연결되어 있다.
예전 방식의 문제점
예전 방식은 이랬다. SQL을 직접 작성하고, 실행하고, 결과를 받아서 일일이 자바 객체에 옮겨 담았다. 회원 정보를 조회하려면 SELECT 문을 작성하고, 결과에서 id를 꺼내고, name을 꺼내고, email을 꺼내서 User 객체에 넣었다.
문제는 이런 작업이 계속 반복된다는 거다. User, Product, Order, Review... 테이블이 10개면 비슷한 코드를 10번 작성해야 한다. 지루하고 실수하기 쉽다.
객체와 테이블의 패러다임 차이
더 큰 문제가 있다. 자바는 객체로 생각하는데, 데이터베이스는 테이블로 생각한다. 이 둘의 사고방식이 다르다.
① 상속 문제
자바에서는 부모 클래스를 만들고 자식 클래스를 만들 수 있다. 근데 데이터베이스 테이블에는 상속이라는 개념이 없다. 어떻게 저장할까? 복잡해진다.
② 관계 문제
자바에서는 "이 주문은 누가 했지?" 하면 order.getUser()처럼 참조로 바로 접근한다. 데이터베이스는? 외래키를 써서 JOIN을 해야 한다. 방식이 완전히 다르다.
③ 타입 문제
자바의 날짜 타입과 데이터베이스의 날짜 타입이 다르다. true/false도 데이터베이스마다 다르게 표현한다.
이런 차이 때문에 개발자는 항상 "객체 → 테이블", "테이블 → 객체" 변환 작업을 해야 했다. 귀찮고 실수도 많이 생긴다.
3. ORM이 나온 이유
이 문제를 해결하기 위해 ORM(Object-Relational Mapping)이 등장했다. 이름 그대로 객체(Object)와 관계형 데이터베이스(Relational)를 연결(Mapping)해주는 기술이다.
┌─────────────────────┐
│ 자바 객체 │
│ User, Order... │
└──────────┬──────────┘
│
│ ORM이 자동 변환
│
┌──────────▼──────────┐
│ 데이터베이스 테이블 │
│ users, orders... │
└─────────────────────┘
쉽게 말하면, 자바 객체를 저장하면 자동으로 테이블에 들어간다. 테이블을 조회하면 자동으로 자바 객체로 나온다. SQL을 직접 안 써도 된다.
💡 핵심 개념
DB를 SQL로 다루는 게 아니라, 자바 객체로 다룬다는 게 핵심이다.
예전에는 "INSERT INTO users..." 이렇게 SQL을 작성했다면, ORM을 쓰면 userRepository.save(user) 이렇게 객체만 저장하면 끝이다. 내부적으로 ORM이 알아서 SQL을 만들어서 실행한다.
개발자는 테이블 구조를 신경 쓸 필요가 없다. 그냥 자바 클래스만 생각하면 된다. Python, Ruby, .NET 등 대부분의 언어가 ORM을 제공한다.
4. JPA는 자바의 ORM 표준이다
JPA는 자바에서 ORM을 쓰기 위한 표준 규칙이다. 중요한 건, JPA는 "이렇게 사용하세요"라는 설명서일 뿐이고, 실제로 동작하는 구현체는 따로 있다는 것이다.
마치 USB 규격이 정해져 있고, 실제로는 삼성, LG 등 여러 회사가 USB를 만드는 것과 비슷하다.
┌──────────────────────────┐
│ JPA (표준 인터페이스) │
│ "이렇게 사용하세요" │
└───────────┬──────────────┘
│
│ 구현
▼
┌──────────────────────────┐
│ Hibernate (99% 사용) │
│ EclipseLink │
│ DataNucleus │
└───────────┬──────────────┘
│
│ 실행
▼
┌──────────────────────────┐
│ MySQL / PostgreSQL │
│ Oracle / MariaDB │
└──────────────────────────┘
실제로는 Hibernate라는 구현체를 가장 많이 쓴다. 거의 99%가 Hibernate를 쓴다고 봐도 된다. 그래서 실무에서는 JPA = Hibernate라고 생각해도 크게 틀리지 않다.
표준을 따르면 좋은 점은, 나중에 Hibernate 말고 다른 걸로 바꾸고 싶을 때 코드를 크게 수정하지 않아도 된다는 것이다. 물론 실제로 바꾸는 경우는 거의 없지만.
5. JPA가 해주는 3가지
① 객체와 테이블을 자동으로 연결
자바 클래스에 @Entity 같은 표시를 붙여서 "이 클래스는 데이터베이스의 이 테이블과 연결되어 있어"라고 알려주면 끝이다.
필드 위에 @Column 같은 걸 붙여서 "이 필드는 테이블의 이 컬럼이야"라고 정의한다. @Id는 "얘가 기본키야"라는 뜻이고.
이렇게 한 번만 설정하면, 이후로는 그냥 이 객체를 다루기만 하면 된다. JPA가 알아서 SQL을 만들어준다.
② CRUD를 객체로 처리
| 작업 | 방법 | 결과 |
|---|---|---|
| 저장 | 객체를 저장 메서드에 넣기 | INSERT 쿼리 자동 실행 |
| 조회 | ID로 조회 | SELECT 쿼리 실행 → 객체 반환 |
| 수정 | 객체 값 변경만 하기 | UPDATE 쿼리 자동 실행 (변경 감지) |
| 삭제 | 삭제 메서드에 객체 넣기 | DELETE 쿼리 실행 |
수정이 특히 신기하다. 객체의 값을 바꾸기만 하면 된다. user.setAge(26) 이렇게. 별도로 update 메서드를 호출하지 않아도 JPA가 "어? 값이 바뀌었네?" 하고 자동으로 UPDATE 쿼리를 날린다. 이걸 변경 감지(Dirty Checking)라고 부른다.
SQL을 하나도 안 쓴다. 객체만 다룬다. JPA가 중간에서 SQL로 바꿔준다.
③ 반복 작업 제거
예전에는 회원 조회 코드, 상품 조회 코드, 주문 조회 코드... 전부 비슷한데 계속 반복해서 작성했다.
JPA를 쓰면 한 번 설정만 하면 CRUD는 거의 패턴이 똑같다. 코드량이 확 줄어든다. 개발자는 "어떻게 데이터베이스에 저장할까?" 대신 "이 비즈니스 로직을 어떻게 처리할까?"에 집중할 수 있다.
💡 핵심 정리
즉, JPA는 데이터베이스를 자바 객체 관점에서 다루게 해주는 도구다.
6. 영속성 컨텍스트: JPA의 작업 공간
JPA를 이해하려면 영속성 컨텍스트(Persistence Context)라는 개념을 알아야 한다. 어려운 이름인데, 쉽게 말하면 JPA의 임시 저장소다.
🎮 게임으로 비유하면
게임에서 아이템을 줍는다고 바로 서버에 저장되지 않는다. 일단 내 가방(캐시)에 담겨 있다가, 마을에 도착해서 저장(세이브)하면 그때 서버에 반영된다.
아이템 줍기 → 가방에 임시 보관 → 마을에서 저장 → 서버에 반영
↓ ↓ ↓ ↓
객체 저장 영속성 컨텍스트 트랜잭션 커밋 DB 반영
JPA도 마찬가지다. 객체를 저장하거나 조회하면 일단 영속성 컨텍스트라는 임시 공간에 담긴다. 진짜 데이터베이스에는 트랜잭션이 끝날 때 한꺼번에 반영된다.
왜 이렇게 할까? (5가지 장점)
① 1차 캐시
같은 데이터를 계속 조회해도 데이터베이스에 한 번만 가면 된다. 두 번째부터는 캐시에서 꺼내온다. 빠르다.
② 똑같은 객체 보장
ID가 1번인 회원을 두 번 조회하면, 진짜로 똑같은 객체를 준다. user1 == user2가 true다.
③ 쓰기 지연
저장 명령을 10번 내려도 10번 INSERT를 안 한다. 모아뒀다가 트랜잭션 끝날 때 한 방에 실행한다. 효율적이다.
④ 변경 감지
객체 값을 바꾸면 JPA가 "원래 값이랑 비교해서 바뀐 게 있네?" 하고 자동으로 UPDATE 쿼리를 만든다.
⑤ 지연 로딩
연결된 데이터는 실제로 필요할 때 가져온다. 당장 필요 없으면 안 가져와서 성능이 좋다.
7. 엔티티의 4가지 상태
JPA가 관리하는 객체(엔티티)는 4가지 상태가 있다.
┌─────────────┐
│ 비영속(new) │ 그냥 객체 생성
└──────┬──────┘
│ persist()
▼
┌─────────────┐
│ 영속(managed)│ JPA가 관리 중 ⭐ 핵심 기능 동작
└──────┬──────┘
│ detach()
▼
┌──────────────┐
│준영속(detached)│ JPA 관리 중단
└──────┬───────┘
│ remove()
▼
┌─────────────┐
│삭제(removed) │ 삭제 예정
└─────────────┘
상태별 설명
| 상태 | 설명 | 예시 |
|---|---|---|
| 비영속 | 그냥 객체를 만든 상태 | User user = new User(); |
| 영속 | JPA가 관리하는 상태 | 저장/조회하면 영속 상태 |
| 준영속 | JPA가 관리하다가 떼어낸 상태 | JPA 기능이 안 먹힘 |
| 삭제 | 삭제 예정 | 트랜잭션 끝나면 DB에서 삭제 |
⚠️ 중요
영속 상태일 때만 JPA의 핵심 기능들이 동작한다는 게 중요하다.
8. JPQL: 객체로 쿼리 작성하기
기본적인 조회, 저장, 삭제는 JPA가 알아서 해준다. 근데 "나이가 20살 이상인 회원 찾기" 같은 조건 검색은? 복잡한 통계는?
이럴 때 JPQL을 쓴다. SQL과 비슷한데, 테이블이 아니라 객체(엔티티)를 대상으로 쿼리를 작성한다.
SQL vs JPQL 비교
| 구분 | SQL | JPQL |
|---|---|---|
| 대상 | 테이블 | 엔티티(객체) |
| 예시 | SELECT * FROM users WHERE age >= 20 |
SELECT u FROM User u WHERE u.age >= 20 |
| 이름 | users (테이블명) | User (엔티티명) |
왜 JPQL을 쓸까?
데이터베이스가 바뀌어도 코드 수정이 필요 없다. JPQL을 쓰면 JPA가 알아서 MySQL, PostgreSQL, Oracle 등 사용 중인 데이터베이스에 맞는 SQL로 바꿔준다.
테이블 이름이 users에서 member로 바뀌어도 엔티티 이름만 수정하면 JPQL은 그대로 쓸 수 있다.
9. 연관관계: 객체끼리 연결하기
실제 프로젝트에서는 테이블이 혼자 있는 경우가 거의 없다. 회원과 주문은 연결되어 있고, 주문과 상품도 연결되어 있다.
연관관계 매핑은 이런 관계를 JPA에서 표현하는 방법이다.
객체 vs 테이블의 관계 표현 방식
| 구분 | 방식 | 예시 |
|---|---|---|
| 테이블 | 외래키 + JOIN | SELECT * FROM orders o JOIN users u ON o.user_id = u.id |
| 객체 | 참조 | order.getUser() |
JPA는 이 차이를 중간에서 해결해준다. 자바 코드로는 order.getUser()로 쓰면, JPA가 내부적으로 JOIN 쿼리를 만들어서 실행한다.
연관관계의 종류
1️⃣ 방향성
- 단방향: A가 B를 참조. 한쪽만 알고 있음.
- 양방향: A가 B를 참조하고, B도 A를 참조. 서로 알고 있음.
2️⃣ 다중성
| 관계 | 설명 | 예시 |
|---|---|---|
| 일대다 (1:N) | 하나가 여러 개를 가짐 | 한 회원이 여러 주문 |
| 다대일 (N:1) | 여러 개가 하나에 속함 | 여러 주문이 한 회원에게 |
| 일대일 (1:1) | 일대일 대응 | 회원 한 명당 프로필 하나 |
| 다대다 (N:M) | 서로 여러 개 | 학생과 수업 (중간 테이블 필요) |
10. 즉시 로딩 vs 지연 로딩
주문을 조회할 때 주문한 회원 정보도 같이 가져올까, 나중에 필요할 때 가져올까?
두 가지 전략 비교
| 구분 | 즉시 로딩 (EAGER) | 지연 로딩 (LAZY) ⭐ 권장 |
|---|---|---|
| 동작 | 주문 + 회원 정보 함께 조회 | 주문만 조회, 필요할 때 회원 조회 |
| 쿼리 | JOIN 한 번에 | SELECT 여러 번 |
| 장점 | 한 번에 다 가져옴 | 필요한 것만 가져옴 |
| 단점 | 불필요한 데이터도 로딩 | 추가 쿼리 발생 가능 |
⚠️ N+1 문제
N+1 문제란 하나의 쿼리를 실행했는데 추가로 N개의 쿼리가 더 실행되는 문제다.
주문 목록 100개 조회 (쿼리 1번)
└→ 각 주문마다 회원 조회 (쿼리 100번 추가!)
= 총 101개 쿼리 실행 😱
이걸 해결하려면 fetch join 같은 기술을 써야 한다. 한 번에 JOIN해서 가져오는 거다.
💡 실무 팁
실무에서는 지연 로딩을 기본으로 쓴다. 즉시 로딩은 필요 없는 데이터까지 다 가져와서 느려질 수 있기 때문이다.
11. JPA를 쓰는 이유
✅ 6가지 장점
1️⃣ 생산성이 높다
SQL 안 써도 되고, 반복 코드가 없다. 빠르게 개발할 수 있다.
2️⃣ 유지보수가 쉽다
테이블에 컬럼 추가하면 엔티티에 필드만 추가하면 끝. SQL 찾아다니며 수정 안 해도 됨.
3️⃣ 객체 지향으로 개발
데이터베이스 생각 안 하고 자바 객체만 생각하면 됨.
4️⃣ 성능 최적화
캐시, 지연 로딩 같은 기능으로 최적화 가능. 잘 쓰면 빠르다.
5️⃣ 데이터베이스 바꾸기 쉬움
MySQL에서 PostgreSQL로 바꿔도 코드 수정 거의 없음.
6️⃣ 표준이라 레퍼런스 많음
막히면 검색하면 다 나옴.
12. JPA의 단점
⚠️ 4가지 주의사항
1️⃣ 배우기 어렵다
영속성 컨텍스트, 지연 로딩, N+1 문제 등 알아야 할 게 많다. 잘못 쓰면 오히려 느려진다.
2️⃣ 복잡한 쿼리는 힘들다
통계, 복잡한 JOIN은 JPQL로 작성하기 어렵다. 이럴 땐 직접 SQL을 쓰거나 QueryDSL 같은 다른 도구를 쓴다.
3️⃣ 성능 문제 생길 수 있음
N+1 문제, 불필요한 쿼리 실행 등. JPA가 만드는 SQL을 확인하고 튜닝할 줄 알아야 한다.
SQL 몰라도 된다는 게 아니라, SQL 알아야 JPA를 잘 쓸 수 있다.
4️⃣ 대량 데이터 처리는 부적합
몇만 건씩 처리하는 배치는 JPA보다 직접 SQL 쓰는 게 나을 수 있다.
13. Spring Data JPA
실무에서는 JPA를 직접 쓰기보다 Spring Data JPA를 쓴다. JPA를 더 쉽게 쓸 수 있게 스프링에서 만든 거다.
주요 기능
📝 자동 구현
인터페이스만 만들면 구현 코드를 자동으로 만들어준다.
🔍 메서드 이름으로 쿼리 생성
findByName, findByAgeGreaterThan 이런 메서드 이름만 정의하면, 이름을 보고 자동으로 쿼리를 만들어준다.
📄 페이징, 정렬
페이징, 정렬 같은 기능도 쉽게 쓸 수 있다.
💡 실무 표준
실무에서는 JPA + Spring Data JPA 조합이 거의 표준이다.
14. 정리
🎯 한 줄 요약
JPA는 자바에서 데이터베이스를 객체로 다루게 해주는 도구다.
핵심 내용
✅ 장점
- SQL을 직접 안 써도 됨
- 객체만 다루면 알아서 DB에 저장/조회됨
- 반복 코드 줄어들고 생산성 높아짐
⚠️ 주의사항
- 배우기 어려움
- 복잡한 쿼리는 힘듦
- SQL을 모르면 안 됨 (SQL 알아야 JPA 잘 씀)
🎪 적용 기준
- 적합: 단순 CRUD 많은 경우
- 부적합: 복잡한 통계, 대용량 배치
마치며
처음에는 "SQL도 안 쓰는데 어떻게 동작하지?" 하고 신기하고 불안하다. 하지만 영속성 컨텍스트 같은 개념을 이해하면, 객체 중심으로 편하게 개발할 수 있다는 걸 알게 된다.
JPA를 마스터하려면 SQL을 알아야 하고, 영속성 컨텍스트를 이해해야 하며, N+1 문제를 해결할 줄 알아야 한다.