JPA 장점
- 우선 CRUD SQL을 작성할 필요가 없다.
- JPA가 제공하는 네이티브 SQL 기능을 사용해서 직접 SQL을 작성할 수도 있고, 데이터베이스 쿼리 힌트도 사용할 수 있는 방법이 있다.(성능걱정x)
- 개발 단계에서 MySQL DB를 사용하다가 오픈 시점에 갑자기 Oracle로 바꿔도 코드를 거의 수정할 필요가 없다.
강의 : JPA 소개 - SQL 중심적인 개발의 문제점
SQL 직접 다룰 때 발생하는 문제점
반복 또 반복
// 1. 회원 등록용 SQL 작성
String sql = "INSERT INTO MEMBER(MEMBER_ID,NAME) VALUES(?,?)";
//2. 회원 객체의 값을 꺼내서 등록 SQL에 전달한다.
pstmt.setString(1, member.getMemberId();
pstmt.setString(2, member.getName());
//3.JDBC API를 사용해서 SQL을 실행한다.
pstmt.executeUpdate(sql);
- 객체를 데이터베이스에 CRUD 하려면 너무 많은 SQL과 JDBC API를 코드로 작성해야 한다는 점이다.
- 그리고 테이블마다 이런 비슷한 일을 반복해야 하는데, 개발하려는 애플리케이션에서 사용하는 데이터베이스 테이블이 100개라면 무수히 많은 SQL을 작성해야 하고 이런 비슷한 일은 100번은 더 반복해야 한다.
- 데이터 접근 계층 (DAO)을 개발하는 일은 이렇듯 지루함과 반복의 연속이다.
SQL에 의존적인 개발
갑자기 회원의 연락처도 함께 저장해달라는 요구사항이 추가 되었다. = 새롭게 쿼리를 다 짜야한다...
등록코드 변경
public class Member{
private String memberId;
private String name;
private String tel; //추가
...
}
연락처를 저장할 수 있도록 INSERT SQL을 수정했다.
String sql = "INSERT INTO MEMBER (MEMBER_ID, NAME, TEL) VALUE(?,?,?)";
그 다음 회원 객체의 연락처 값을 꺼내서 등록 SQL에 전달했다.
pstmt.setString(3, member.getTel());
조회 코드 변경
다음 처럼 회원 조회용 SQL을 수정한다.
SELECT MEMBER_ID, NAME, TEL FROM MEMBER WHERE MEMBER_ID = ?
//조회 결과를 Member 객체에 추가로 매핑한다.
String tel = rs.getString("TEL");
member.setTel(tel); //추가
JPA와 문제해결
JPA를 사용하면 객체를 데이터베이스에 저장하고 관리할 때, 개발자가 직접 SQL을 작성하는 것이 아니라 JPA가 제공하는 API를 사용하면 된다. 그러면 JPA가 개발자 대신에 적절한 SQL을 생성해서 데이터베이스에 전달한다.
저장 기능
jpa.persist(member); //저장
조회 기능
String memberId = "helloId";
Member member = jpa.find(Member.class, memberId); //조회
수정 기능
Member member = jpa.find(member.class, memberId);
member.setName("이름변경") // 수정
- JPA는 별도의 수정 메서드를 제공하지 안흔다. 대신에 객체를 조회해서 값을 변경만 하면 트랜잭션을 커밋할 때 데이터베이스에 적절한 UPDATE SQL이 전달된다. (마법 같은 일-> 추후에 공부)
연관된 객체 조회
Member member = jpa.find(Member.class. memberId);
Team team = member.getTeam(); //연관된 객체 조회
JPA는 연관된 객체를 사용하는 시점에 적절한 SELECT SQL을 실행한다. 따라서 JPA를 사용하면 연관된 객체를 마음껏 조회할 수 있다.(마법 같은 일2->추후에 공부)
패러다임의 불일치
- 객체의 기능은 클래스에 정의되어 있으므로 객체 인스턴스의 상태인 속성만 저장 했다가 필요할 때 불러 와서 복구하면 된다.
- 관계형 데이터베이스는 데이터 중심으로 구조화되어 있고, 집합적인 사고를 요구한다. 그리고 객체 지향에서 이야기하는 추상화, 상속, 다형성 같은 개념이 없다.
상속

위의 관계형데이터에서 만약 Album을 조회한다.
1. 각각의 테이블에 따른 조인 SQL 작성
2. 각각의 객체 생성
3. 상상만 해도 복잡...
4. 더 이상 설명은 생략한다.
5. 그래서 DB에 저장할 객체에는 상속 관계 안쓴다!!!
만약 자바 컬렉션에 저장하면?
list.add(album);
자바 컬렉션을 조회하면?
Album album = list.get(albumId); //부모 타입으로 조회 후 다형성 활용 Item item = list.get(albumId);
JDBC를 이용한 상속 구현
INSERT INFO ITEM ...
INSERT INTO ALBUM ...
JPA를 이용한 상속 구현
JPA는 상속과 관련된 패러다임의 불일치 문제를 개발자 대신 해결해준다. 개발자는 마치 자바 컬렉션에 객체를 저장 하듯이 JPA에게 객체를 저장하면 된다.
jpa.persist(album);
연관관계
- 객체는 참조를 사용 : member.getTeam()
- 테이블은 외래 키 사용 : JOIN ON M.TEAM_ID = T.TEAM_ID

- 테이블에 맞춘 객체 모델
class Member {
String id; // MEMBER_ID 컬럼 사용
Long temId; // TEAM_ID FK 컬럼 사용
String username; // USERNAME 컬럼 사용
}
class Team {
Long id; // TEAM_ID PK 사용
String name; // NAME 컬럼 사용
}
그런데 여기서 TEAM_ID 외래 키의 값을 그대로 보관하는 teamId 필드에는 문제가 있다. 객체는 연관된 객체의 참조를 보관해야 다음처럼 참조를 통해 연관된 객체를 찾을 수 있어야 하는데 하지 못하게 된다.
엔티티를 직접 참조하는 것과 간접 참조하는 것에 대한 장단점이 무엇이 있을까?
직접 참조
장점 : 연관된 데이터를 한번에 추출할 수 있다.단점 : 연관된 데이터에 대한 수정이 발생할 경우 영향의 범위가 커질 수 있다.
간접 참조
장점 : 복잡도를 낮출 수 있고, 응집도를 높이고 결합도를 낮출 수 있다.단점 : 연관된 데이터를 한번에 추출 하려면 구현해야 하는 로직이 복잡하다.
- 참조를 사용하는 객체 모델
class Member {
String id; // MEMBER_ID 컬럼 사용
Long temId; // TEAM_ID FK 컬럼 사용
Team team; // 참조로 연관관계를 맺는다.
}
class Team {
Long id; // TEAM_ID PK 사용
String name; // NAME 컬럼 사용
}
JDBC를 이용한 연관 관계 구현
만약 JDBC로 저장하는 로직을 만들기 위해서는 team 필드를 TEAM_ID 외래 키 값으로 변환해야 한다.
member.getId(); // MEMBER_ID PK에 저장
member.getTeam().getId(); // TEAM_ID FK에 저장
member.getUsername(); // USERNAME 컬럼에 저장
또는 조회하는 로직에도 객체를 생성하고 연관관계를 설정해서 반환하는 로직이 필요하다.
public Member find(String memberId) {
// SQL 실행
...
Member member = new Member();
...
// 데이터베이스에서 조회한 회원 관련 정보를 모두 입력
Team team = new Team();
...
// 데이터베이스에서 조회한 팀 관련 정보를 모두 입력
// 회원과 팀 관계 설정
member.setTeam(team);
return member;
}
JPA와 연관관계 구현
개발자는 회원과 팀의 관계를 설정하고 회원 객체를 저장하면 된다. JPA는 team의 참조를 외래 키로 변환해서 적절한 INSERT SQL을 데이터베이스에 전달한다.
member.setTeam(team);
jpa.persist(member);
객체를 조회할 때 외래 키를 참조로 변환하는 일도 JPA가 처리 해준다.
Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam();
객체 그래프 탐색

class MemberService {
...
public void process() {
Member member = memberDAO.find(memberId);
member.getTeam(); // member->team 객체 그래프 탐색이 가능한가?
member.getOrder().getDelivery(); // ???
}
}
위의 코드에서 memberDAO.find에 주목하자. 그것은 무엇을 조회했을까? 무엇을 조회했길래 '팀 정보(getTeam)', '배송+주문 정보'를 가져올수 있을까? 알수없다. 즉, find구현한 Repo를 가서 무슨 쿼리인지를 까봐야한다. 즉 위의 코드는 신뢰가 안간다.
따라서 신뢰를 갖기 위해서는 다음과 같은 코드가 탄생한다.(복잡)
memberDAO.getMember(); //Member만 조회
memberDAO.getMemberWithTeam(); //Member+Team 조회
member.DAO.getMemberWithOrderWIthDelivery(); //Member, Order, Delivery
JPA와 객체 그래프 탐색
JPA를 사용하면 연관된 객체를 신뢰하고 마음껏 조회할 수 있다. 이 기능은 실제 객체를 사용하는 기점까지 데이터베이스 조회를 미룬다고 해서 지연 로딩이라 한다.
// 처음 조회 시점에 SELECT MEMBER SQL
Member member = jpa.find(Member.class, memberId);
Order order = member.getOrder();
order.getOrderDate(); // Order를 사용하는 시점에 SELECT ORDER SQL
JDBC로 구현한 비교
데이터베이스와 같은 로우로 조회했지만 객체의 동일성 비교에는 실패한다.
class MemberDAO {
public Member getMember(String memberId) {
String sql = "SELECT * FROM MEMBER WHERE MEMBER_ID + ?";
...
// JDBC API, SQL 실행
return new Member(...);
}
}
String memberId = "100";
Member member1 = memberDAO.getMember(memberId);
Member member2 = memberDAO.getMember(memberId);
member1 == member2; // false
JPA로 구현한 비교(=자바 컬렉션)
String memberId = "100";
Member member1 = jpa.find(Member.class,memberId);
Member member2 = jpa.find(Member.class,memberId);
member1 == member2; // true
정리
데이터 주도 설계와 도메인 주도 설계의 장단점은 무엇일까?
강의 JPA소개 - JPA소개
JPA란 무엇인가?
ORM?


왜 JPA를 사용해야 하는가?
- 생산성
이전에 DAO에서 작업하던 지루하고 반복적인 일은 JPA가 대신 처리 해준다. 이런 기능들을 사용하면 데이터베이스 설계 중심의 패러다임을 객체 설계 중심으로 역전시킬 수 있다.
- 유지보수
이전엔 엔티티 필드 하나만 수정해도 관련된 DAO 로직의 SQL문을 모두 변경해야 했다. 반면에 JPA는 대신 처리해주므로 필드를 추가하거나 삭제해도 수정해야 할 코드가 줄어든다.
- 패러다임의 불일치 해결
- 성능
JPA는 애플리케이션과 데이터베이스 사이에 동작하여 최적화 관점에서 시도해 볼 수 있는 것들이 많다. 예를 들어 동일한 조건으로 조회 했을 경우엔 SELECT SQL을 한 번만 데이터베이스에 전달하고 두 번째 조회한 회원 객체는 재사용할 수 있다.
- 데이터 접근 추상화와 벤더 독립성
관계형 데이터베이스는 같은 기능도 벤더 마다 사용법이 다른 경우가 많다. 단적인 예로 페이징 처리는 데이터베이스마다 달라서 사용법을 각각 배워야 한다. 결국 애플리케이션은 데이터베이스에 종속되어 변경 하기는 매우 어렵다. **JPA는 추상화된 데이터 접근 계층을 제공해서 애플리케이션이 특정 데이터베이스 기술에 종속되지 않도록 할 수 있다.**
생산성
저장 : jpa.persist(member)
조회 : Member member = jpa.find(memberId)
수정 : member.setName("변경할 이름")
삭제 : jpa.remove(member)
JPA의 성능 최적화 기능
- 1차 캐시와 동일성 보장
- 트랜잭션을 지원하는 쓰기 지연 - INSERT
- 지연 로딩과 즉시 로딩
지연로딩
Member member = memberDAO.find(memberId); //selcet * from member
Team team = member.getTeam();
String teamName = team.getName(); //select * from team
즉시로딩
Member member = memberDAO.find(memberId); //select M.*, T.* from member join team
Team team = member.getTeam();
String teamName = team.getName();
JPA, Hibernate, Spring Data JPA 차이점
JPA(=껍데기)
JPA공부를 시작함에 있어서 가장헷갈렸던 부분이 JPA와 Hibernate와의 관계였다.
동영상강의에서는 처음에 EntityManager를 활용하여 Data를 삭제 저장 업데이트를 하지만, 실제 실무에서는 EntityManager를 사용하지 않고 Repository 인터페이스 만을 이용해서 JPA를 사용한다.
JPA는 Java Persistence API의 약자로, 자바 어플리케이션에서 관계형 데이터베이스를 사용하는 방식을 정의한 인터페이스이다. 여기서 중요하게 여겨야 할 부분은, JPA는 말 그대로 인터페이스라는 점이다. JPA는 특정 기능을 하는 라이브러리가 아니다. 마치 일반적인 백엔드 API가 클라이언트가 어떻게 서버를 사용해야 하는지를 정의한 것처럼, JPA 역시 자바 어플리케이션에서 관계형 데이터베이스를 어떻게 사용해야 하는지를 정의하는 한 방법일 뿐이다.
JPA는 단순히 명세이기 때문에 구현이 없다. JPA를 정의한 javax.persistence 패키지의 대부분은 interface, enum, Exception, 그리고 각종 Annotation으로 이루어져 있다. 예를 들어, JPA의 핵심이 되는 EntityManager는 아래와 같이 javax.persistence.EntityManager 라는 파일에 interface로 정의되어 있다.
package javax.persistence;
import ...
public interface EntityManager {
public void persist(Object entity);
public <T> T merge(T entity);
public void remove(Object entity);
public <T> T find(Class<T> entityClass, Object primaryKey);
// More interface methods...
}
Hibernate(JPA를 구현한 구현체)
Hibernate는 JPA 명세의 구현체이다. javax.persistence.EntityManager와 같은 JPA의 인터페이스를 직접 구현한 라이브러리이다.

위 사진은 JPA와 Hibernate의 상속 및 구현 관계를 나타낸 것이다. JPA의 핵심인 EntityManagerFactory, EntityManager, EntityTransaction을 Hibernate에서는 각각 SessionFactory, Session, Transaction으로 상속받고 각각 Impl로 구현하고 있음을 확인할 수 있다.
“Hibernate는 JPA의 구현체이다”로부터 도출되는 중요한 결론 중 하나는 JPA를 사용하기 위해서 반드시 Hibernate를 사용할 필요가 없다는 것이다. Hibernate의 작동 방식이 마음에 들지 않는다면 언제든지 DataNucleus, EclipseLink 등 다른 JPA 구현체를 사용해도 되고, 심지어 본인이 직접 JPA를 구현해서 사용할 수도 있다. 다만 그렇게 하지 않는 이유는 단지 Hibernate가 굉장히 성숙한 라이브러리이기 때문일 뿐이다.
Spring Data JPA
필자는 Spring으로 개발하면서 단 한 번도 EntityManager를 직접 다뤄본 적이 없다. DB에 접근할 필요가 있는 대부분의 상황에서는 Repository를 정의하여 사용했다. 아마 다른 분들도 다 비슷할 것이라 생각한다. 이 Repository가 바로 Spring Data JPA의 핵심이다.
Spring Data JPA는 Spring에서 제공하는 모듈 중 하나로, 개발자가 JPA를 더 쉽고 편하게 사용할 수 있도록 도와준다. 이는 JPA를 한 단계 추상화시킨 Repository라는 인터페이스를 제공함으로써 이루어진다. 사용자가 Repository 인터페이스에 정해진 규칙대로 메소드를 입력하면, Spring이 알아서 해당 메소드 이름에 적합한 쿼리를 날리는 구현체를 만들어서 Bean으로 등록해준다.
Spring Data JPA가 JPA를 추상화했다는 말은, Spring Data JPA의 Repository의 구현에서 JPA를 사용하고 있다는 것이다. 예를 들어, Repository 인터페이스의 기본 구현체인 SimpleJpaRepository의 코드를 보면 아래와 같이 내부적으로 EntityManager을 사용하고 있는 것을 볼 수 있다.
'Back-end > JPA' 카테고리의 다른 글
엔티티 매핑 (0) | 2022.08.06 |
---|---|
[JPA] JPA 영속성 컨텍스트 (0) | 2022.05.05 |
[JPA] Batch Insert (0) | 2022.05.05 |
자바 ORM 표준 JPA 프로그래밍 (0) | 2022.03.14 |
모든 연관관계는 지연로딩으로 설정하자. (N+1문제) (0) | 2022.03.13 |