01. JPA와 모던 데이터 저장 기술

james·2021년 7월 8일
0

아직까지는 관계형 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

  • Java Persistence API
  • 자바 진영의 ORM 표준 기술

ORM?

  • Object-relational mapping(객체 관계 매핑)
  • 객체는 객체대로 설계
  • 관계형 DB는 관계형 DB대로 설계
  • ORM 프레임워크가 중간에서 매핑
  • 대중적인 언어에는 대부분 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 2.1 표준 명세를 구현한 3가지 구현체
  • 하이버네이트, EclipseLink, DataNucleus

JPA를 왜 사용해야 하는가?

  • SQL 중심적인 개발에서 객체 중심으로 개발
  • 생산성
  • 유지보수
  • 패러다임의 불일치 해결
  • 성능
  • 데이터 접근 추상화 벤더 독립성
  • 표준

생산성 - JPA와 CRUD
코드가 다 만들어져 있음

  • 저장: jpa.persist(member) - insert 쿼리 만들어서 DB에 저장
  • 조회: Member member = jpa.find(memberId) - 객체 타입이랑 식별자만 넣으면 member 객체가 튀어 나오고 select쿼리 나옴
  • 수정: member.setName("변경할 이름")- db에서 Update 쿼리가 수행됨.
  • 삭제: jpa.remove(member)

유지보수

  • 기존: 필드 변경시 모든 SQL 수정
  • JPA: 필드만 추가하면 됨, SQL은 JPA가 처리

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
  1. JPA와 연관관계
    // 연관관계 저장
    member.setTeam(team);
    jpa.persist(member);
  2. JPA와 객체 그래프 탐색
    // 객체 그래프 탐색
    Member member = jpa.find(Member.class, memberId);
    Team team = member.getTeam();
  • 신뢰할 수 있는 엔티티, 계층
    앞에서 처음 실행된 SQL에 따라 탐색범위가 결정되기 때문에 마음대로 엔티티를 호출할 수 없었음. 하지만 JPA를 통해서 member 객체를 가져온거면 객체 그래프를 정말 자유롭게 탐색할 수 있음. JPA는 지연로딩이라는 기능이 있어서 조회하는 시점에 SQL 쿼리문이 실행되면서 데이터가 채워짐.
    그 말인 즉슨, JPA를 통해 가져온 member 객체는 믿을 수 있음.
    class MemberService {
        public void process() {
            Member member = memberDAO.find(memberId);
            member.getTeam(); // 자유로운 객체 그래프 탐색
            member.getOrder().getDelivery();
    }
  1. JPA와 비교하기
  • 동일한 트랜잭션에서 조회한 엔티티는 똑같은 엔티티임을 보장해줌
String memberId = "100";
Member member1 = jpa.find(Member.class, memberId);
Member member2 = jpa.find(Member.class, memberId);

member1 == member2; // 같다.

JPA의 성능 최적화 기능
1. 1차 캐시와 동일성(identity) 보장

    1. 같은 트랜잭션 안에서는 같은 엔티티를 반환 - 약간의 조회 성능 향상
    1. DB Isolation Level이 Read Commit이어도 애플리케이션에서 Repeatable Read 보장

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
  1. 트랜잭션을 지원하는 쓰기 지연(transacional write-behind)
  • 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();	//[트랜잭션] 커밋
  1. 지연 로딩(Lazy Loading)
  • 지연 로딩: 객체가 실제 사용될 때 로딩

  • 즉시 로딩: 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 두 기둥위에 있는 기술.

profile
명확하게 표현할 수 없으면 모르는거다.

0개의 댓글

관련 채용 정보