아직까지는 관계형 DB - Oracle, MySql
지금 시대 객체는 관계형 DB
SQL 중심적인 개발의 문제점
무한 반복, 지루한 코드
CRUD 쿼리
자바 객체를 SQL로 SQL을 자바 객체로(무한 노가다.....)
객체 CRUD
public class Member {
private String memberId;
private String name;
private String tel;
}
쿼리문을 다 수정해야 하는 문제가 발생
SQL에 의존적인 개발을 피하기 어렵다.
패러다임의 불일치
객체 vs 관계형 데이터베이스(데이터를 잘 보관해서 운용하는 목적)
-이에 따른 문제가 발생
객체를 영구 보관하는 다양한 저장소
Object를 다양한 곳에 저장할 수 있지만 현실적인 대안은 관계형 DB이다.
객체를 관계형 데이터베이스에 저장
객체 - SQL 변환
개발자가 거의 SQL매퍼의 일을 수행 중.
객체와 관계형 데이터베이스의 차이
1. 상속 - o / x
2. 연관관계 - o / x(join)
3. 데이터 타입
4. 데이터 식별 방법
차이 나는 걸 매칭시키는 과정
그나마 유사한 모델
객체 상속 관계 - Table 슈퍼타입 서브타입 관계
슈퍼타입:
서브타입:
Album 객체를 저장
Album 조회
ITEM이랑 ALBUM을 Join한 이후에 Album에 맞는 걸 Item에 다 집어 넣어야 함. 여기서 끝나느냐? 만약에 MOVIE를 조회한다. MOVIE랑 ITEM에 join 쿼리를 만들고 가져온 결과를 Movie를 생성해서 값을 채워넣고 반환해줘야 함. BOOK 역시 마찬가지(반복의 연속)
자바 컬렉션 저장
자바 컬렉션 조회 - Simple
SQL 쿼리문을 짜서 작성하는게 복잡
연관관계
객체는 - Team에서 Member로 갈 수 없음(단방향)
테이블은 - 양방향으로 왔다갔다
객체를 테이블에 맞추어 모델링
class Member {
String id; // MEMBER_ID 컬럼 사용
Long teamId; // TEAM_ID FK 컬럼 사용
String username; // USERNAME 컬럼 사용
}
class Team {
Long id; // TEAM_ID PK 사용
String name; // NAME 컬럼 사용
}
테이블에 맞춘 객체 저장
INSERT INTO MEMBER(MEMBER_ID, TEAM_ID, USERNAEM) VALUES ...
문제가 발생?
객체지향적이지 않은 것 같다??
DB에 외래키를 보면 무언가의 참조인 것 같아.
아래처럼 설계를 바꿔봄.
class Member {
String id; // MEMBER_ID 컬럼 사용
Team team; // 참조로 연관관계를 맺는다.
String username; // USERNAME 컬럼 사용
Team getTeam() {
return team;
}
}
class Team {
Long id; // TEAM_ID PK 사용
String name; // NAME 컬럼 사용
}
INSERT INTO MEMBER(MEMBER_ID, TEAM_ID, USERNAEM) VALUES
객체 모델링 저장하는데 문제 발생
TEAM_ID 외래키값이 없고 참조값만 있는 문제를 발생
그래서 이 문제를 해결하기 위해서
member.getTeam().getId();
객체의 getId()를 이용해서 TEAM_ID에 값을 넣어줌.
그런데 이렇게 하면 조회를 할 때 문제가 발생하게 된다.
아래의 코드처럼 너무 번거로움.
SELECT M.*, T.*
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
public Member find(String memberId) {
// SQL 실행 ...
Member member = new Member();
// 데이터베이스에서 조회한 회원 관련 정보를 모두 입력
Team team = new Team();
// 데이터베이스에서 조회한 팀 관련 정보를 모두 입력
// 회원과 팀 관계 설정
member.setTeam(team); // **
return member;
}
객체 모델링, 자바 컬렉션에 관리 DB에 넣는 순간 생산성이 안 나옴.
객체 그래프 탐색
호출을 마음대로 못하는 문제점.
처음 실행하는 SQL에 따라서 탐색범위가 결정이 되버림.
SELECT M.*, T.*
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
member.getTeam(); // OK
member.getOrder(); // null - why? ORDER값은 안 채웠으니까.
엔티티 신뢰 문제
class MemberService() {
public void process() {
Member member = memberDAO.find(memberId);
member.getTeam();
member.getOrder().getDelivery();
}
}
모든 객체를 미리 로딩할 수는 없다.
상황에 따라 동일한 회원 조회 메서드를 여러번 생성
계층형 아키텍처 - 진정한 의미의 계층 분할이 어렵다.
비교하기
String memberId = "100";
Member member1 = memberDAO.getMember(memberId);
Member member2 =
자바 컬렉션에서 조회
참조값이 같기 때문에 같다.
객체답게 모델링 할수록 매핑 작업만 늘어나고 더 힘들어짐.
객체를 자바 컬렉션에 저장 하듯이 DB에 저장할 수는 없을까?
그에 대한 해결책은 JPA
JPA
ORM?
JPA 동작 - 저장
JPA에게 Member 객체를 넘기면 JPA가 Member 객체를 분석을 함.
JPA가 적절한 INSERT 쿼리문 생성
JPA가 JDBC API를 사용해서 DB에 쿼리를 보내고 결과를 받음(개발자가 만드는 게 아님)
패러다임의 불일치를 해결.
JPA 동작 - 조회
JPA가 적절한 SELECT 쿼리문을 생성 - 매핑 정보를 바탕으로
JDBC API를 이용해서 DB에 쿼리문을 보내고 결과를 받음.
결과값을 ResultSet을 이용해서 객체에다가 매핑을 시켜줌.
패러다임 불일치 해결
(코드 한줄로 JPA가 번잡한 작업을 다해줌)
JPA 소개
EJB - 엔티티 빈 (자바 표준)
하이버네이트 (오픈 소스)
JPA (자바 표준)
JPA는 표준 명세
JPA를 왜 사용해야 하는가?
생산성 - JPA와 CRUD
코드가 다 만들어져 있음
유지보수
JPA와 패러다임의 불일치 해결
1. JPA와 상속
jpa.persist(album);
// JPA가 알아서 쿼리문을 2개 생성
INSERT INTO ITEM ...
INSERT INTO ALBUM ...
// 개발자가 할 일
Album album = jpa.find(Album.class, albumId);
// 나머진 JPA가 처리
SELECT I.*, A.*
FROM ITEM I
JOIN ALBUM A ON I.ITEM_ID = A.ITEM_ID
// 연관관계 저장
member.setTeam(team);
jpa.persist(member);
// 객체 그래프 탐색
Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam();
class MemberService {
public void process() {
Member member = memberDAO.find(memberId);
member.getTeam(); // 자유로운 객체 그래프 탐색
member.getOrder().getDelivery();
}
String memberId = "100";
Member member1 = jpa.find(Member.class, memberId);
Member member2 = jpa.find(Member.class, memberId);
member1 == member2; // 같다.
JPA의 성능 최적화 기능
1. 1차 캐시와 동일성(identity) 보장
JPA는 처음에 SQL이 날아가서 m1값을 가져옴
두번째 똑같은 PK값이 들어가면 JPA는 메모리에 있는 member1을 그대로 반환해줌.(짧은 시간의 캐싱-실제 실무에서는 크게 도움은 안됨)
결과적으로 SQL 1번만 실행
String memberId = "100";
Member member1 = jpa.find(Member.class, memberId); // SQL
Member member2 = jpa.find(Member.class, memberId); // 캐시
println(member1 == member2); // true
INSERT
- 01. 트랜잭션을 커밋할 때까지 INSERT SQL을 모음
- 02. JDBC BATCH SQL 기능을 사용해서 한번에 SQL 전송
transaction.begin(); // [트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
// 여기까지 INSERT SQL을 DB에 보내지 않는다.
// 커밋하는 순간 DB에 INSERT SQL을 모아서 보낸다.
transaction.commit(); // [트랜잭션] 커밋
UPDATE
- 01. UPDATE, DELETE로 인한 로우(ROW)락 시간 최소화
- 02. 트랜잭션 커밋 시 UPDATE, DELETE SQL 실행하고, 바로 커밋
transaction.begin(); //[트랜잭션] 시작
changeMember(memberA);
deleteMember(memberB);
비즈니스_로직_수행(); // 비즈니스 로직 수행 동안 DB 로우 락이 걸리지 않는다.
// 커밋하는 순간 DB에 UPDATE, DELETE SQL을 보낸다.
transaction.commit(); //[트랜잭션] 커밋
지연 로딩: 객체가 실제 사용될 때 로딩
즉시 로딩: JOIN SQL로 한번에 연관된 객체까지 미리 조회
// 지연 로딩 - member객체를 꺼낼 때는 member만 가져옴
// team객체에 값이 실제 필요한 시점에서 JPA가 DB에 TEAM에 대한 쿼리를 날려서 객체에 대한 식별자 값을 가져와서 데이터를 채워서 채워진 결과인 teamName을 반환
Member member = memberDAO.find(memberID); // SELECT * FROM MEMBER
Team team = member.getTeam();
String teamName = team.getName(); // SELECT * FROM TEAM
// 즉시 로딩
// 지연 로딩의 문제점: 쿼리가 2번 나감, 네트워크를 2번 타야함.
// 애플리케이션 개발을 자주 하다보니 member를 조회하면 거의 team을 같이 조회하는 경향이 높다면 처음에 한방 쿼리로 team을 같이 가져오는 게 나음.
// JPA 옵션 활성화시키면 쿼리가 바뀜.
// 이후에 member와 team을 조회할 때는 로딩되어있는 데이터를 가지고 사용.
Member member = memberDAO.find(memberId); // SELECT M.*, T.* FROM MEMBER JOIN TEAM
Team team = member.getTeam();
String teamName = team.getName();
ORM은 객체와 RDB 두 기둥위에 있는 기술.