1장. JPA 소개
(1) SQL을 직접 다룰 때 발생하는 문제점
관계형 데이터베이스는 가장 대중적이고 신뢰할 만한 안전한 데이터 저장소이다.
데이터베이스에 데이터를 관리하려면 SQL을 사용해야 한다.
자바로 작성한 애플리케이션은 JDBC API를 사용해서 SQL을 데이터베이스 전달한다.
엔티티 : 비즈니스 요구사항을 모델링한 객체를 의미한다.
불필요한 반복
개발자는 데이터 접근 계층 (DAO)를 통해 SQL과 JDBC API를 사용해서 직접 변환 작업을 해주어야한다.
객체를 데이터베이스에 CRUD 하려면 매우 많은 SQL 과 JDBC API를 코드로 작성해야 하는 문제가 발생한다.
비슷한 작업을 계속해서 반복하는 것은 불필요한 작업이다.
public class MemberDAO {
// 회원 조회용 SQL
public Member find(String memberId) {
String sql = "SELECT MEMBER_ID, NAME FROM MEMBER M WHERE MEMBER_ID = ?";
ResultSet rs = stmt.executeQuery(sql);
String memberId = rs.getString("MEMBER_ID");
String name = rs.getString("NAME");
Member member = new Member();
member.setMemberId(memberId);
member.setName(name);
}
// 회원 등록용 SQL
public void save(Member member) {
String sql = "INSERT INTO MEMBER(MEMBER_ID, NAME) VALUES (?, ?)";
pstmt.setString(1, member.getMemberId());
pstmt.setString(2, member.getName());
}
}
SQL에 의존적인 개발
1. 진정한 의미의 계층 분할이 어렵다.
**물리적으로 SQL과 JDBC API를 데이터 접근 계층에 숨기는 데 성공해도 논리적으로는 엔티티와 아주 강한 의존관계를 가지고 있다.
2. 엔티티를 신뢰할 수 없다.
DAO를 열어서 어떤 SQL이 실행되고 어떤 객체들이 함께 조회되는지 일일이 확인해야 한다.
3. SQL에 의존적인 개발을 피하기 어렵다.
JPA와 문제 해결
JPA를 사용하면 객체를 데이터베이스에 저장하고 관리할 때, 개발자가 직접 SQL을 작성하는 것이 아니라 JPA가 제공하는 API를 사용하면 된다.
그러면 JPA가 개발자 대신 적절한 SQL을 생성해서 데이터베이스에 전달한다.
저장 기능
persist()
메소드는 객체를 데이타베이스에 저장
jpa.persist(member); // 저장
조회 기능
find()
메소드는 객체 하나를 데이터베이스에서 조회한다.
String memberId = "helloId";
Member member = jpa.find(Member.class, memberId); // 조회
수정 기능
JPA는 별도로 수정 메소드를 제공하지 않는다.
대신 객체를 조회해서 값을 변경만 하면 트랜잭션 커밋할 때 데이터베이스에 적절한 UPDATE SQL이 전달된다.
Member member = jpa.find(Member.class, memberId);
member.setName("이름변경"); // 수정
연관된 객체 조회
JPA는 연관된 객체를 사용하는 시점에 적절한 SELECT SQL을 실행한다.
Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam(); // 연관된 객체 조회
(2) 패러다임의 불일치
애플리케이션이 발전하면서 내부의 복잡성도 점점 커진다.
객체지향 프로그래밍은 추상화
, 캡슐화
, 정보은닉
, 상속
, 다형성
등 시스템의 복잡성을 제어할 수 있는 다양한 장치들을 제공한다.
객체는 속성(필드)과 기능(메소드)을 가진다.
자바는 특정 객체에서 참조하는 객체의 정보도 함께 저장하기 위해 객체를 파일로 저장하는 직렬화 기능
과 저장된 파일을 객체로 복구하는 역 직렬화 기능
을 지원한다.
=> 직렬화된 객체를 검색하기 어렵다는 문제, 현실성 X
따라서 현실적인 대안은 관계형 데이터베이스에 객체를 저장하는 것.
관계형 데이터베이스는 데이터 중심으로 구조화되어 있고, 집합적인 사고를 요구한다. 그리고 추상화, 상속 다형성 같은 개념이 없다
객체와 관계형 데이터베이스의 패러다임 불일치 문제
객체와 관계형 데이터베이스는 지향하는 목적이 서로 다르므로 둘의 기능과 표현 방법도 다르다.
상속
객체는 상속이라는 기능을 가지고 있지만 테이블은 상속이라는 기능이 없다.
객체 상속 모델
테이블 설계
객체 모델 코드
abstract class Item {
Long id;
String name;
int price;
}
class Album extends Item {
String artist;
}
class Movie extends Item {
String director;
String actor;
}
class Book extends Item {
String author;
String isbn;
}
저장 시 SQL
객체를 분해해서 두 SQL을 만들어야 한다.
// Album 객체 저장
INSERT INTO ITEM ...
INSERT INTO ALBUM ...
// Movie 객체 저장
INSERT INTO ITEM ...
INSERT INTO MOVIE ...
관련 처리
부모와 자식 데이터를 따로 삽입해야 한다.
- 부모 객체에서 부모 데이타만 꺼냄
- ITEM용 INSERT SQL 작성
- 자식 객체에서 자식 데이터만 꺼내서 ALBUM INSERT SQL 작성
- (조회) ITEM과 ALBUM 테이블을 조인해서 그 결과를 다시 Album 객체 생성
컬렉션 스타일로 바꾸면
부모 자식이나 타입에 대한 고민 없이 해당 컬렉션을 그냥 사용하면 된다.
list.add(album);
list.add(movie);
Album album = list.get(albumId);
JPA와 상속
JPA는 상속과 관련한 패러다임의 불일치 문제를 개발자 대신 해결해준다.
자바 컬렉션에 객체를 저장하듯이 JPA에게 객체를 저장한다.
JPA 저장
jpa.persist();
JPA는 다음 SQL을 실행해서 객체를 ITEM, ALBUM 두 테이블에 나누어 저장한다.
INSERT INTO ITEM ...
INSERT INTO ALBUM ...
JPA 조회
String albumId = "id100";
Album album = jpa.find(Album.class, albumId);
JPA는 ITEM과 ALBUM 두 테이블을 조인해서 필요한 데이터를 조회하고 결과를 반환한다.
SELECT I._, A._
FROM ITEM I
JOIN ALBUM A ON I.ITEM_ID = A.ITEM_ID
연관관계
- 객체는 참조를 사용해서 다른 객체와 연관관계를 가지고 참조에 접근해서 연관된 객체를 조회한다.
- 테이블은 외래 키를 사용해서 다른 테이블과 연관관계를 가지고 조인을 사용해서 연관된 테이블을 조회한다.
- 객체는 참조가 있는 방향으로만 조회할 수 있지만, 테이블은 외래 키 하나로 양방향 조회가 가능하다.
객체를 테이블에 맞춰 모델링
- 관계형 데이터베이스 방식에 맞추면 Member 객체와 연관된 Team 객체를 참조를 통해서 조회할 수 없다.
- 좋은 객체 모델링은 기대하기 어렵고 결국 객체지향의 특징을 잃어버린다.
// 테이블에 맞춘 객체 모델
class Member {
String id; // MEMBER_ID 컬럼 사용
Long teamId; // TEAM_ID FK 컬럼 사용 -> Team 객체 참조 불가능
String userName;
}
class Team {
Long id; // TEAM_ID PK 사용
String name;
}
객체지향 모델링
- 객체지향 모델링을 사용하면 객체를 테이블에 저장하거나 조회하기는 쉽지 않다.
- 객체 모델은 외래 키가 필요 없고 단지 참조만 있으면 된다.
- 테이블은 참조가 필요 없고 외래 키만 있으면 된다.
- 결국, 개발자가 중간에서 변환 역활을 해야 한다.
// 참조를 사용하는 객체 모델
class Member {
String id;
Team team; // 참조로 연관관계를 맺는다.
String username;
Team getTeam() {
return team;
}
}
class Team {
Long id;
String name;
}
JPA와 연관관계
JPA는 연관관계와 관련한 패러다임 불일치 문제 를 해결해준다.
member.setTeam(team); // 회원과 팀 연관관계 설정
jpa.persist(member); // 회원과 연관관계 함께 저장
객체를 조회할 때 외래 키를 참조로 변환하는 일도 JPA가 처리
Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam();
개발자는 회원과 팀의 관계를 설정하고 회원 객체를 저장하면 된다.
객체 그래프 탐색
객체에서 회원이 소속된 팀을 조회할 때 참조를 사용해서 연관된 팀을 찾으면 되는데 이것을 객체 그래프 탐색
이라 한다.
member.getOrder().getOrderItem()... // 자유로운 객체 그래프 탐색
그러나 SQL을 직접 다루면 처음 실행하는 SQL에 따라 객체 그래프를 어디까지 탐색할 수 있을지 정해진다.
어디까지 객체 그래프 탐색이 가능한지 알려면 DAO 를 열어서 SQL을 직접 확인해야 가능한지 알 수 있다. (엔티티가 SQL에 논리적으로 종속되어서 발생하는 문제)
// 회원 조회 비지니스 로직
class MemberService {
...
public void process() {
Member member = memberDAO.find(memberId);
member.getTeam(); // member->team 객체 그래프 탐색이 가능한가?
member.getOrder().getDelivery(); // ???
}
}
그렇다고 member
와 연관된 모든 객체 그래프를 데이터베이스에서 조회해서 애플리케이션 메모리에 올려두는 것은 현실성이 없다.
결국 MemberDAO
에 회원을 조회하는 메소드를 상황에 따라 여러 벌 만들어서 사용해야 한다.
memberDAO.getMember(); //Member만 조회
memberDAO.getMemberWithTeam(); // Member와 Team 조회
memberDAO.getMemberWithOrderWithDelivery(); // Member와 Order와 Delivery 조회
JPA는 이러한 문제를 해결해준다.
JPA와 객체 그래프 탐색
JPA를 사용하면 객체 그래프를 마음껏 탐색할 수 있다.
member.getOrder().getOrderItem()... // 자유로운 객체 그래프 탐색
JPA는 연관된 객체를 사용하는 시점에 적절한 SELECT SQL 을 실행한다.
지연로딩 : 실제 객체를 사용하는 시점까지 데이타베이스 조회를 미룬다.
투명한 엔티티
class Member {
private Order order;
public Order getOrder() {
return order;
}
}
지연로딩 사용
// 처음 조회 시점에 SELECT MEMBER SQL
Member member = jpa.find(Member.class, memberId);
Order order = member.getOrder();
order.getOrderDate(); // Order를 사용하는 시점에 SELECT ORDER SQL
성능을 위해 Member
를 조회하는 시점에 SQL 조인
을 사용해서 Member
와 Order
를 함께 조회할 수 있다.
=> JPA는 연관된 객체를 즉시 함께 조회할지, 실제 사용되는 시점에 지연해서 조회할지 간단한 설정으로 정의할 수 있다.
1.2.4 비교
- 데이터베이스 : 기본 키의 값으로 각 ROW를 구분한다.
- 객체
- 동일성(Identity) 비교: 객체 인스턴스의 주소 값을 비교. (
==
) - 동등성(Equality) 비교: 객체 내부의 값을 비교. (
equals()
)
- 동일성(Identity) 비교: 객체 인스턴스의 주소 값을 비교. (
// MemberDAO 코드
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; //다르다.
member1
과 member2
는 같은 데이터베이스 로우에서 조회했지만, 객체 측면에서 볼 때 둘은 다른 인스턴스이다.
=> MemberDAO.getMember()
를 호출할 때마다 new Member()
로 인스턴스가 새로 생성된다.
만약 객체를 컬렉션에 보관했다면 다음과 같이 동일성 비교에 성공한다
Member member1 = list.get(0);
Member member2 = list.get(0);
member1 == member2; // 같다.
JPA와 비교
JPA는 같은 트랜잭션일 때 같은 객체가 조회되는 것을 보장한다.
// member1과 member2는 동일성 비교에 성공
String memberId = "100";
Member member1 = jpa.find(Member.class, memberId);
Member member2 = jpa.find(Member.class, memberId);
member1 == member2; //같다.
정리
- 객체 모델과 관계형 데이터베이스 모델은 지향하는 패러다임이 서로 다르다.
- 객체지향 애플리케이션답게 정교한 객체 모델링을 할수록 패러다임의 불일치 문제가 더욱 커진다.
- JPA는 이런 패러다임의 불일치 문제를 해결해주고 정교한 객체 모델링을 유지하게 도와준다.
(3) JPA란 무엇인가
JPA(Java Persistence API) 는 자바 진영의 ORM 기술 표준이다
어플리케이션과 JDBC 사이에서 동작한다.
ORM (Object Relational Mapping)
객체와 테이블을 매핑해서 패러다임의 불일치 문제를 개발자 대신 해결해준다.
덕분에 개발자는 데이터 중심인 관계형 데이터베이스를 사용해도 객체지향 애플리케이션 개발에 집중할 수 있다.
객체를 저장하는 코드
객체를 DB에 저장할 때 INSERT SQL을 직접 작성하는 것이 아니라 객체를 마치 자바 컬렉션에 저장하듯이 ORM 프레임워크에 저장.
jpa.persist(member); // 저장
객체를 조회하는 코드
Member member = jpa.find(memberId); // 조회
JPA 소개
엔터프라이즈 자바 빈즈 (EJB) 3.0 에서 하이버네이트를 기반으로 새로운 자바 ORM 기술 표준이 만들어졌는데 이것이 바로 JPA이다.
JPA는 자바 ORM 기술에 대한 API 표준 명세이다.
따라서 JPA를 사용하려면 JPA를 구현한 ORM 프레임워크를 선택해야 한다.
JPA 라는 표준 덕분에 특정 구현 기술에 대한 의존도를 줄일 수 있고 다른 구현 기술로 손쉽게 이동할 수 있는 장점이 있다.
JPA 표준은 일반적이고 공통적인 기능의 모음이다.
왜 JPA를 사용해야 하는가?
생산성
- JPA를 자바 컬렉션에 객체를 저장하듯 JPA에게 저장할 객체를 전달.
- INSERT SQL을 작성하고 JDBC API 사용하는 지루하고 반복적인 일을 JPA가 대신 처리해준다.
- CREATE TABLE같은 DDL문 자동 생성
- 데이터베이스 설계 중심의 패러다임을 객체 설계 중심으로 역전
jpa.persist(member); // 저장
Member member = jpa.find(memberId); // 조회
유지보수
- 엔티티에 필드 추가시 등록, 수정, 조회 관련 코드 모두를 자동으로 변경한다.
- JPA를 사용하면 이런 과정을 JPA가 대신 처리한다.
- 개발자가 작성해야 할 SQL과 JDBC API 코드를 JPA가 대신 처리해줌으로 유지보수해야 하는 코드 수가 줄어든다.
패러다임 불일치 해결
상속, 연관관계, 객체 그래프 탐색, 비교하기(동일성(==)
, 동등성(equals())
) 같은 패러다임 불일치 문제를 해결해준다.
성능
- 다양한 성능 최적화 기회 제공
- 어플리케이션과 데이터베이스 사이에 존재하여서 여러 최적화 시도 가능
- JDBC API를 사용해서 회원을 조회하면 할 때 마다
SELECT SQL
을 사용해서 DB와 두 번 통신해야 하는 반면,
JPA를 사용하면 회원 조회SELECT SQL
을 한 번만 데이터베이스에 전달하고 두 번째는 조회한 회원 객체를 재사용한다.
데이터 접근 추상화와 벤더 독립성
- 데이터베이스 기술에 종속되지 않도록 한다.
- 데이터베이스를 변경하면 JPA에게 다른 데이터베이스를 사용한다고 알려주면 됨.
표준
- 자바 진영의 ORM 기술의 표준이다.