1. SQL을 직접 다룰 때의 문제점
1.1. 반복 그리고 반복
// Member 객체
public class Member {
private String memberId;
private String name;
...
}
// Member용 Data Access Object
public class MemberDAO {
public Member find(String memberId) {...}
}
Java
복사
위와 같은 DAO에서 회원을 찾기 위한 find 메서드를 구현할 때 아래와 같은 순서로 개발을 진행한다.
1.
회원 조회용 SQL 작성
SELECT MEMBER_ID, NAME FROM MEMBER WHERE MEMBER_ID = ?
SQL
복사
2.
JDBC API를 사용해 SQL을 실행
ResultSet rs = stmt.executeQuery(sql);
Java
복사
3.
조회 결과를 Member 객체로 매핑
String memberId = rs.getString("MEMBER_ID");
String name = rs.getString("NAME");
Member member = new Member(memberId, name);
...
Java
복사
여기서 다른 기능(회원 추가, 삭제 등)을 추가할 경우 위 3가지 경우를 지속적으로 반복하게 된다.
•
결국 문제는 객체를 DB에 CRUD하려면 너무 많은 SQL과 JDBC API를 코드로 작성해야한다.
•
테이블이 100개라면 무수히 많은 SQL을 작성해야 하고 이런 비슷한 일을 100번은 더 반복해야 한다.
1.2. SQL에 의존적인 개발
앞서 진행한 예제에서 만약 요구사항이 추가되어 회원 테이블에 전화번호를 저장할 수 있도록 변경되었다고 생각해보자
// Member 객체
public class Member {
private String memberId;
private String name;
private String tel; // 추가 됨
...
}
Java
복사
이렇게 새롭게 요구사항이 추가되어 테이블이 변경될 경우 이에 따른 객체와 SQL문 전부가 수정되어야 한다.
•
수정되어야 할 코드의 양도 많을 뿐더러 수정되지 않은 SQL로 인해서 버그가 발생할 수도 있다.
SQL 의존적인 개발은 SQL 계층을 분리해 숨겨도 문제를 찾기 위해서는 어쩔 수 없이 DAO를 열어 실행되는 SQL을 확인해야한다.
•
진정한 의미의 계층 분할이 어렵고, 엔티티를 신뢰할 수 없다.
1.3. JPA와 문제 해결
JPA를 사용하면 객체를 데이터베이스에 저장하고 관리할 때, 개발자가 직접 SQL을 작성하는 것이 아닌 JPA가 제공하는 API를 이용하면 된다.
그러면 JPA가 개발자 대신 적절한 SQL을 생성해 DB에 전달한다.
1.3.1. 저장기능
jpa.persist(member);
Java
복사
•
persist() 메소드는 객체를 데이터베이스에 저장한다.
•
JPA가 객체와 매핑정보를 보고 적절한 SQL을 생성해 DB에 전달한다.
1.3.2. 조회기능
String memberId = "helloId";
Member member = jpa.find(Member.class, memberId);
Java
복사
•
find() 메소드는 객체 하나를 DB에서 조회한다.
1.3.3. 수정기능
String memberId = "helloId";
Member member = jpa.find(Member.class, memberId);
member.setName("이름변경");
Java
복사
•
JPA는 별도의 수정 메소드를 제공하지 않는다.
•
대신 객체를 조회해 값을 변경하면 트랜잭션을 커밋할 때 적절한 UPDATE SQL이 전달된다.
1.3.4. 연관된 객체 조회
String memberId = "helloId";
Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam();
Java
복사
•
JPA는 연관된 객체를 사용하는 시점에 적절한 SELECT SQL을 실행한다.
2. 패러다임 불일치
•
애플리케이션은 발전하며 그 내부의 복잡성이 점점 커진다.
◦
복잡성을 제어하지 못하면 결국 유지보수하기 어려운 애플리케이션이 된다.
•
객체지향 프로그래밍은 추상화, 캡슐화, 정보은닉, 상속, 다형성 등 시스템의 복잡성을 제어할 수 있는 다양한 장치들을 제공한다.
•
비즈니스 요구사항을 정의한 도메인 모델도 객체로 모델링 하면 객체지향 언어가 가진 장점을 활용할 수 있다.
◦
But. 저장이 필요한 객체라면 메모리가 아닌 어딘가에 영구 보관해야한다.
◦
자바는 이런 문제를 고려해 객체를 파일로 저장하는 직렬화 기능을 지원한다.
▪
But. 보안상의 문제와 더불어 객체를 검색하기가 어렵다.
◦
현실적인 대안은 RDB를 사용하는 것이지만 이는 앞서 이야기한 객체지향적인 개념이 존재하지 않는다.
•
객체지향과 RDB간의 패러다임 불일치를 개발자가 중간에서 해결해야 한다.
2.1. 상속
객체의 상속 모델
RDB의 슈퍼타입과 서브타입 모델
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;
}
Java
복사
객체 모델 코드
•
객체는 상속이라는 기능을 가지고 있지만 테이블은 상속이라는 기능이 없다.
◦
그나마 유사한 모델링은 슈퍼타입과 서브타입 관계라고 말할 수 있다.
•
ITEM 테이블의 DTYPE 컬럼을 사용해 어떤 자식 테이블과 관계가 있는지 정의 했다.
•
JDBC API를 사용해 해당 코드를 완성하려면 ITEM용 INSERT SQL을 작성하고 자식 데이터만 커내 ALBUM용 INSERT SQL을 작성해야 하는데 작성해야 하는 코드량이 만만치 않다.
◦
물론 조회도 쉬운일이 아닌것이 ALBUM을 조회한다면 ITEM과 ALBUM테이블을 조인해 결과로 ALBUM 객체를 생성해야 한다.
•
자바 컬렉션에 보관한다면 이런 고민없이 그냥 컬렉션에 보관하면 된다.
list.add(album);
list.add(movie);
Album album = list.get(albumId);
Java
복사
•
JPA는 상속과 관련된 패러다임 불일치 문제를 개발자 대신 해결해준다.
◦
개발자는 마치 자바 컬렉션에 객체를 저장하듯 JPA에게 객체를 저장하면 된다.
◦
jpa.persist(album);
2.2. 연관관계
•
객체는 참조를 사용해 다른 객체와 연관관계를 가지고 참조에 접근해 연관된 객체를 조회한다.
◦
반면 테이블은 외래 키를 사용해 테이블과 연관관계를 가지고 조인을 사용해 연관된 테이블을 조회한다.
연관관계
•
참조를 사용하는 객체와 외래 키를 사용하는 RDB 사이의 패러다임 불일치는 객체지향 모델링을 거의 포기하게 말든 정도록 극복하기 어렵다.
•
Member 객체는 참조를 통해서 Tream을 조회할 수 있다.
// 참조를 사용하는 객체 모델
class Member {
String id;
Team team;
String username;
Team getTeam() {
return team;
}
}
class Team {
Long id;
String name;
}
...
member.getTeam(); // 접근
Java
복사
•
MEMBER 테이블은 MEMBER.TEAM_ID 외래 키를 통해 TEAM 테이블과 관계를 맺고 조인하여 접근할 수 있다.
SELECT M.*, T.* FROM MEMBER M JOIN TEAM T M.TEAM_ID = T.TEAM_ID;
SQL
복사
2.2.1. 객체를 테이블에 맞춰 모델링
class Member {
String id;
Long teamId;
String username;
}
class Team {
Long id;
String name;
}
Java
복사
테이블에 맞춘 객체 모델
•
여기서 조금 어려운 문제가 존재한다.
◦
객체는 참조가 있는 방향으로만 조회가 가능하지만 외래 키는 반대 방향으로도 조회가 가능하다.
◦
테이블과 동일하게 객체 모델을 수정하게 되면 Member 객체와 연관된 Team 객체를 참조를 통해 조회할 수 없다.
▪
좋은 객체 모델링은 기대하기 어렵고 결국 객체지향의 특징을 잃어버리게 된다.
2.2.2. 객체지향 모델링
•
참조를 통한 모델링을 이용할 경우 개발자가 중간에서 변환 역할을 해줘야 한다.
•
이에는 저장/조회시 개발자가 사용하고 신경써야하는 비용이 된다.
2.3. 객체 그래프 탐색
객체 연관관계
•
객체에서 회원이 소속된 팀을 조회할 때는 다음처럼 참조를 사용해 연관된 침을 찾으면 된다 ⇒ 이를 그래프 탐색이라 한다.
◦
member.getOrder().getOrderItem()... = 그래프 탐색 코드
•
SQL을 직접 다루면 처음 실행하는 SQL에 따라 객체 그래프를 어디까지 탐색할 수 있는지 정해진다.
◦
이는 객체지향 개발자에겐 큰 제약인데 비즈니스 로직에 따라 객체 그래프가 다른데 언제 끊어질지 모를 그래프를 함부로 탐색할 수는 없기 때문
class MemberService {
...
public void process() {
Member member = memberDAO.find(memberId);
member.getTeam(); // 그래프 탐색이 가능한가?
member.getOrder().getDelivery(); // ???
}
}
Java
복사
•
member 객체를 조회했지만, 객체와 연관된 Team, Order, Delivery 방향으로 객체 그래프를 탐색할 수 있을지 없을지는 이 코드만으로 전혀 예측할 수 없다.
◦
SQL에 논리적으로 종속되어서 발생하는 문제다.
2.3.1. JPA와 객체 그래프 탐색
•
JPA를 사용하면 객체 그래프를 마으껏 탐색할 수 있다.
•
JPA는 지연로딩을 투명하게 처리한다.
Member member = jpa.find(Member.class, memberId);
Order order = member.getOrder();
order.getOrderDate(); // Order를 사용하는 시점에 SELECT ORDER SQL
Java
복사
•
JPA는 연관된 객체를 즉시 함께 조회할지 아니면 실제 사용되는 시점에서 지연해서 조회할지를 간단한 설정으로 정의 가능하다.
2.4. 비교 (Compare)
•
DB는 기본 키의 값으로 각 로우를 구분한다.
•
반면 객체는 동일성과 동성성이라는 두 가지 비교방법이 있다.
◦
== 동일성 비교로 인스턴스 주소가 동일한지 비교한다.
◦
equals() 동등성 비교로 인스턴스 내부의 값을 비교한다.
•
JDBC API를 이용해서 값을 매핑 시킬 경우 동일한 값으로 조회하더라도 동일한 객체가 아닌 동등한 객체가 된다.
◦
같은 로우를 조회할 때마다 같은 인스턴스를 반환하도록 구현하는 것은 쉽지 않다.
◦
트랜잭션이 동시에 실행되는 상황까지 고려하면 문제는 더 어려워진다.
•
JPA는 같은 트랜잭션일 때 같은 객체가 조회되는 것을 보장한다.
2.5. 정리
•
객체 모델과 RDB 모델은 지향하는 패러다임이 서로 다르다.
◦
문제는 이 패러다임의 차를 극복하기 위해 개발자가 너무 많은 시간과 코드를 소비한다는 점이다.
•
패러다임의 불일치로 객체 모델링은 힘을 잃고 점점 데이터 중심의 모델로 변해간다.
•
JPA는 패러다임의 불일치 문제를 해결해주고 정교한 객체 모델링을 유지하게 도와준다.
3. JPA란 무엇인가?
JPA
•
JPA(Java Persistence API)는 자바 진영의 ORM 기술 표준으로 애플리케이션과 JDBC 사이에서 동작한다.
•
그렇다면 ORM은 무엇인가?
◦
Object-Relational Mapping은 이름 그대로 객체와 RDB를 매핑한다는 뜻이다.
◦
ORM 프레임워크를 사용한다면 패러다임 불일치 문제를 개발자 대신 해결해준다.
JPA 저장
JPA 조회
jpa.persist(member); // 저장
jpa.find(member); // 조회
Java
복사
•
ORM 프레임워크는 단순히 SQL을 개발자 대신 생성해 데이터베이스에 전달해주는 것뿐만 아니라 앞서 이야기한 다양한 패러다임의 불일치 문제들도 해결해준다.
◦
객체 측면에서는 정교한 객체 모델링 가능
◦
객체와 모델링간 매핑은 ORM에게만 알려주면 된다.
◦
개발자는 데이터 중심인 RDB를 사용해도 객체지향 애플리케이션 개발에 집중 가능
•
성숙한 객체지향 언어들은 대다수 ORM 프레임워크들이 있고, 단순 CRUD 기능만 제공하거나 패러다임 불일치 문제를 대부분 해결해주는 프레임워크도 존재한다.
◦
자바 진형은 하이버네이트가 거의 대부분의 패러다임 불일치 문제를 해결해준다.
3.1. JPA 소개
•
과거 자바 진영은 자바 빈즈(EJB)라는 표준 기술을 만들었는데 그 안에는 엔티티 빈이라는 ORM 기술도 포함되어 있었다.
◦
But. 너무 복잡, 성숙도 떨어짐 J2EE 환경에서만 동작했다는 단점이 존재했다.
•
이때 하이버네이트가 등장했고 EJB 3.0에서 하이버네이트를 기반으로 새로운 자바 ORM 표준이 만들어진게 JPA다.
JPA 표준 인터페이스와 구현체
•
JPA는 자바 ORM 기술에 대한 API 표준 명세다.
◦
쉽게 이야기 인터페이스를 모아놓은 것
◦
JPA 2.1 를 구현한 ORM 구현체는 아래와 같다.
▪
하이버네이트, EclipseLink, DataNucleus
•
JPA의 버전별 특징
◦
JPA 1.0 (JSR 220) = 2006년 출시된 초기버전, 복합 키와 연관관계 기능이 부족
◦
JPA 2.0 (JSR 317) = 2009년 대부분의 orm 기능을 포함하고 JPA Criteria가 추가
◦
JPA 2.1 (JSR 338) = 2013년 스토어드 프로시저 접근, 컨버터, 엔티티 그래프 기능 추가
3.2 왜 JPA를 사용해야 하는가?
3.2.1. 생산성
•
JPA를 사용하면 자바 컬력센에 객체를 저장하듯 JPA에게 저장할 객체를 전달하면 된다.
•
SQL을 작성하고 JDBC API를 사용하는 지루하고 반복적인 작업을 JPA가 대신 처리한다.
•
더 나아가 JPA는 CREATE TABLE과 같은 DDL문을 자동으로 생성해주는 기능도 있다.
◦
이런 기능을 활용해 데이터베이스 설계 중심의 패러다임을 객체 설계 중심으로 역전할 수 있다.
3.2.2. 유지보수
•
SQL을 직접 다루면 엔티티에 필드를 하나만 추가해도 관련된 CRUD SQL과 결과를 매핑하기 위한 코드를 모두 변경해야 했다.
•
JPA를 사용하면 이런 과정을 JPA가 대친 처리해주므로 필드를 추가하거나 삭제해도 수정해야할 코드가 줄어든다.
◦
즉 유지보수 해야하는 코드 수가 줄어든다.
•
JPA가 패러다음 불일치를 해결해주므로 객체지향 언어가 가진 장점을 활용해 유연하고 유지보수 하기 좋은 도메인 모델을 설계할 수 있다.
3.2.3. 패러다임 불일치 해결
•
JPA는 상속, 연관관계, 객체 그래프 탐색, 비교하기와 같은 패러다임의 불일치 문제를 해결해준다.
3.2.4. 성능
•
JPA는 애플리케이션과 데이터베이스 사이에서 다양한 성능 최적화 기회를 제공한다.
•
예를 들어 각은 키의 값을 통해 조회시 같은 트랜잭션 내라면 추가적인 통신없이 기존 객체를 재사용할 수 있다.
•
하이버네이트는 SQL 힌트를 넣을 수 있는 기능도 제공한다.
3.2.5. 데이터 접근 추상화와 벤더 독립성
벤더 독립성
•
RDB는 같은 기능도 벤더마다 사용법이 다른경우가 많은데 SQL을 직접 다루는 경우 다른 벤더로의 변경은 매우 어렵다.
•
JPA를 사용하면 특정 기술에 종속적이지 않기 때문에 손쉽게 변경할 수 있다.
3.2.6. 표준
•
JPA는 자바 진영의 ORM 표준 기술이기 때문에 다른 구현 기술로 손쉽게 변경 가능하다.