본문 바로가기

IT공부/JPA

JPA 1강

JPA 1강

JPA 실습준비 

STS IDE 설치(https://spring.io/tools)

  • File > New -> Spring Starter Project
  • Type : Maven 선택 후 Next
  • H2, JPA 선택 후 Finish ( 라이브러리를 미리 받아두는 것이다. 라이브러리받는데 시간이 오래 걸리므로)

H2 데이터베이스 설치(http://www.h2database.com)

  • h2폴더/bin/h2.sh (윈도우 h2.bat) 실행
  • 아래 H2 데이터베이스 설치 과정 참고

STS IDE 설치(https://spring.io/tools) 과정

H2 데이터베이스 설치(http://www.h2database.com) 과정

아래 경로에서 shift 버튼을 누른 상태에서 마우스 우클릭을 한 뒤 "여기에 PowerShell 창 열기"를 클릭한다.

웹 브라우저에서 주소창에 "localhost:8082"를 입력한 뒤 아래와 같이 입력 후 연결 버튼을 누른다.

아래와 같이 에러가 난다. 이유는 존재하지 않는 데이터베이스에 연결하려고 하기 때문이다. 

먼저 데이터베이스를 생성하자.

웹 브라우저 주소창에 "localhost:8082"를 입력하여 이동하자. 아래와 같이 페이지에 입력하고 비밀번호에는 데이터베이스 생성시 설정한 번호를 입력하고 연결 버튼을 누른다.

정상적으로 접속되었다.

이클립스에서 생성한 demo 프로젝트에서 DemoApplication 클래스 메인 메서드를 실행하여 정상적으로 실행되는지 테스트해보자.

요즘 쿠팡, 우아한 형제들에서는 디폴트로 JPA를 베이스로 깔고 들어간다. 스프링 진영에서도 예제 샘플코드가 다 JPA로 되어 있다. 그러면서 국내에서도 점점 확산이 되었다. 

 

어플리케이션 개발은 객체지향언어로 한다. 대부분 JAVA로 개발한다. 데이터베이스는 관계형 DB가 주류를 이루고 있다. 지금 시대는 객체를 관계형 DB에 관리하는 시대이다. 관계형 DB는 SQL을 계속 짜야 한다. 

 

1. SQL 중심적인 개발의 문제점

  • 무한반복, 지루한 코드 : CRUD, INSERT INTO, UPDATE, SELECT, DELETE, 자바 객체를 SQL로, SQL을 자바 객체로

tel 컬럼 하나를 추가해야 한다면 모든 쿼리를 수정해야 한다. 하지만 개발자도 인간이기에 실수로 컬럼을 추가안한 쿼리도 생길 수 있다.

member 객체에서 팀정보를 가져오고 주문정보에서 배송지정보를 가져올 수 있을까? 만약 memberDAO에서 팀정보와 주문정보, 배송지정보를 가져오는 쿼리여야하는데 현실적으로 그게 안된다. memberDAO가 실제로 정보를 다 가져오는지는 클래스 코드를 직접 확인해야 알 수 있다. 직접 코드를 확인해서 팀정보와 주문정보, 배송지정보를 다 가져오는지 확인이 되지 않는 이상 아래와 같이 코드를 짤 수 없다. 진정한 의미의 계층 분할(Controller, Service, Dao(계층형 아키텍처))이 어렵다. 즉, 아래 코드를 짜려면 memberDAO 코드를 직접 확인하기 전까지는 짜기가 힘들다. 물리적으로는 계층이 분할되어 있지만 논리적으로는 분할되었다고 하기 어렵다.  

결국 SQL에 의존적인 개발을 피하기 어렵다. SQL이 어떤식으로 되어 있는지 알기 전까지는 비지니스 로직을 어떻게 개발해야될지 확정짓기 어렵다. 

 

객체지향과 관계형 데이터베이스는 나온 사상이 다르다. 관계형 데이터베이스는 철저하게 데이터를 어떤 식으로 정규화해서 잘 저장할까의 포커싱이 되어 있다. 객체는 데이터를 저장한다는 관점이 아니라 어떻게 잘 추상화하여 관리를 잘 할까라는 관점이다. 이 2가지를 억지로 맵핑을 해서 처리를 해야 되는 상황인 것이다. 그래서 개발자는 중간에서 많은 시간을 보내고 있는 것이다.  

 

객체를 영구 보관하는 다양한 저장소

현실적으로 객체를 저장하기 위해서는 RDB나 NoSQL을 쓸 수 밖에 없다. 지금 현실적인 대안은 관계형 데이터베이스 밖에 없다. 조금 더 나아가서는 NoSQL까지 밖에 없다. 

객체를 관계형 데이터베이스에 저장

객체를 관계형 데이터베이스에 저장하기 위해서는 개발자가 SQL로 변환해야 한다. 예를 들어 DTO 객체가 있으면 코드를 분석해서 SQL로 "INSERT INTO~~" 이런 식으로 쿼리를 개발해야 한다. 쿼리를 짜고 객체로 변환하고 객체를 다시 쿼리문으로 바꾼다. 개발자가 SQL 매퍼 작성을 너무 많이 한다. 

객체와 관계형 데이터베이스의 차이

1. 상속

2. 연관관계

3. 데이터 타입

4. 데이터 식별 방법

 

객체와 관계형 데이터베이스의 차이를 알아야 객체와 SQL 간의 매핑을 어떻게 해야 하는지 알수 있다. 

1. 객체 상속 관계

예를 들어 모 배달업체 회사에서 Item이 있는데 갑자기 책도 팔고 영화표도 팔고 앨범도 배달해야 한다면 아마도 위와 같이 객체 상속 관계를 설계할 것이다. 그렇다면 데이터베이스는 어떻게 설계할 것인가? 예로 부터 데이터베이스에는 이런 식의 설계에 대한 표준화된 방법이 있다. 테이블에 슈퍼타입과 서브타입이라는 논리모델이 있다. 아래 그림을 보면 ITEM 테이블의 ITEM_ID 컬럼으로 다른 테이블들을 조인하여 사용하는 것으로 설계되어 있다. 그리고 ITEM 테이블에는 DTYPE 컬럼으로 구분값을 준다. 데이터베이스에서는 보통 이런 식으로 문제를 해결한다. 

Table 슈퍼타입 서브타입 관계

관계형 데이터베이스에서는 상속이 있을까? 있는 데이터베이스도 있지만 객체스타일의 상속관계가 아니기 때문에 없다고 보는게 맞다. 하지만 객체에서는 상속이 존재한다. 그렇다면 과연 이 문제(객체와 관계형 데이터베이스의 차이)를 어떻게 해결할 것인가? 

 

예를 들어 Album을 저장한다고 해보자. 모 배달 업체에서 이제는 Album도 판다고 해보자. 그래서 위 "객체 상속 관계" 그림과 같이 객체 지향적으로 설계를 했다. 그렇다면 DB에 저장을 하려면 어떻게 해야 할까? 일단은 "INSERT INTO ITEM.."과 "INSERT INTO ALBUM..." 쿼리를 작성해야 한다. 그리고 DAO에서는 객체를 분해해서 이름, 가격, 작곡가로 분리를 하여 이걸 기반으로 쿼리 2개를 짜서 DB에 저장할 것이다. 

이제는 Album 조회를 한다고 해보자. ITEM과 ABLUM을 조인한다. 하지만 ITEM 테이블에 연관된 MOVIE, BOOK 테이블 정보를 가져오려면 그 때 마다 조인쿼리를 새로 짜야 한다. 바로 개발자가 말이다. 그리고 각각의 객체를 생성해서 매핑해야 한다. 생각만 해도 복잡하다. 그래서 DB에 저장할 객체(DTO)에는 상속관계를 쓰지 않는다. 그래서 보통 SuperDTO를 만든다. SuperDTO에는 ALBUM, MOVIE, BOOK 테이블 정보가 다 들어 있다. 그래서 SuperDTO를 사용하여 반환받는다. 

만약 자바 컬렉션에 저장하면?

list.add(ablum);

위 한줄로 끝이난다. DB에 저장을 하려고 하니 복잡해지는 것이다.

자바 컬렉션에서 조회하면?

Album album = list.get(albumId);

 

부모타입으로 조회 후 다형성 활용

Item item = list.get(albumeId);

 

위와 같이 간단하게 처리가능하다. DB에 컨버팅하여 저장, 조회를 하려니 문제가 생기는 것이다. 객체와 DB간의 차이가 발생하기 때문에 중간에 매핑작업 등을 개발자가 해야되기 때문에 많은 코드를 작성해야 하는 것이다. 그래서 보통 객체지향적으로 설계를 하지 않는다. 

2. 연관관계

- 객체는 참조를 사용 : member.getTeam();

- 테이블은 외래 키를 사용 : JOIN ON M.TEAM_ID = T.TEAM_ID

 

member 객체에서 team 객체를 가져오고 싶다면 member.getTeam() 메서드로 가져오면 된다. 

DB에서 MEMBER 테이블로 TEAM 테이블 정보를 가져오고 싶다면 TEAM_ID 외래키로 TEAM 테이블의 TEAM_ID 컬럼을 조인하여 가져올 수 있다. 

사실 위 2개가 비슷한 것 같지만 다르다. 객체에서는 member에서 team으로 갈 수 있지만 team에서 member로 갈 수는 없다. 반대로 DB에서는 MEMBER 테이블에서 TEAM 테이블의 정보를 얻어 올 수도 있고 TEAM 테이블에서 MEMBER테이블로 정보를 얻어올 수도 있다. 바로 조인을 통해서 말이다. 객체의 참조를 통한 연관관계는 방향성이 있다. 반면 DB에서의 foreign key(MEMBER 테이블의 foreign key)라는 것은 방향성이 없다. foreign key 설정이 되어 있다면 양쪽으로 다 정보를 얻어올 수 있다. JPA에서의 포인터(C언어에서 많은 사람들이 포기하는 지점)가 바로 이 지점이다. JPA를 처음 공부하면 연관관계때문에 골머리를 앓는다. 왜 골머리를 앓느냐하면 바로 위 2개의 차이를 몰라서이다. 

 

만약 위 [테이블 연관관계] 그림 처럼 테이블을 설계했다면 객체 설계는 어떻게 해야할까? 

객체를 테이블에 맞추어 모델링

class Member {

   String id;             //MEMBER_ID 컬럼 사용;

   Long teamId;       //TEAM_ID FK 컬럼 사용;

   String username;  //USERNAME 컬럼 사용;

}

 

class Team {

   Long id;        //TEAM_ID PK 사용;

   String name;  //NAME 컬럼 사용;

}

 

테이블과 객체를 1:1로 매핑한다. 객체 지향적이라는 것은 [객체 연관관계] 그림에서 보듯이 Member 클래스에 값이 있는 것이 아니라 Team 객체가 있는게 객체지향적인 것이다. 위와 같이 설계를 하면 teamId만 꺼내올 수 있지 다른 정보를 꺼내올 수 없다. 하지만 보통 이런 식으로 개발을 한다. 왜냐하면 DB에 저장시 쿼리를 쉽게 짤 수 있기 때문이다. 

테이블에 맞춘 객체 저장

아래와 같이 객체다운 모델링으로 다시 개발을 했다.

객체다운 모델링

class Member {

   String id;

   Team team;           //참조로 연관관계를 맺는다.

   String username; 

   

   Team getTeam() {

      return team;

   }

}

 

class Team {

   Long id; 

   String name;

}

 

아래와 같이 저장은 잘된다.

객체 모델링 저장

하지만 조회할 때가 문제이다.

객체 모델링 조회

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;

}

 

위 find() 메서드를 보자. 먼저 조회를 하기 위해 쿼리를 실행하고 Member 객체를 만들고 DB에서 조회한 정보를 Member 객체에 모두 입력하고 다시 Team 객체를 만들고 그 안에 정보를 입력한다. 그리고 member.setTeam(team) 코드를 실행하여 member 객체 안에 team 객체를 세팅하고 member 객체를 리턴한다. 간단해 보이지만 복잡한 코드이다. 

객체 모델링, 자바 컬렉션에 관리

list.add(member);

 

Member member = list.get(memberId);

Team team = member.getTeam();

 

위와 같이 객체에서는 아무 문제가 없다. 그런데 문제는 패러다임이 완전히 다른 관계형 데이터베이스에 넣고 빼는 순간 그 작업량이 폭증하는 것이다. 그래서 버그가 많아져서 효율성이 떨어지는 것이다. 

객체 그래프 탐색

객체는 자유롭게 객체 그래프를 탐색할 수 있어야 한다. 예를 들어 member.getTeam()을 하면 조회할 수 있어야 한다. member 객체에 team이라는 필드가 있다면 조회해야 되는지 고민하면 안되는 것이다. 문맥상 맞으면 바로 조회하는 것이다. DB상에는 MEMBER가 TEAM을 조회할 수 있게 되어 있다. 그런데 실제 서비스 로직을 개발할 때는 못한다. 쿼리에 데이터를 안 넣어놨기 때문이다. 즉, 핵심은 객체 연관관계가 있는 것을 자유롭게 탐색할 수 있어야 한다는 말이다. 하지만 현실은 그렇지 못하다.

 

처음 실행하는 SQL에 따라 탐색 범위 결정

SELECT M.*, T.*

 FROM MEMBER M

   JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID

 

member.getTeam();   //OK

member.getOrder();  //null

 

member.getOrder() 메서드로는 order 값을 가져올 수 없다. 이미 쿼리를 그렇게 짜지 않았기 때문이다. 그렇다면 쿼리도 짯고 데이터 세팅까지 했다면 그 로직을 어디서 확인을 해야할까? DAO 객체를 까봐야 한다. 

 

class MemberService {

   ...

   public void process() {

      Member member = memberDAO.find(memberId);

      member.getTeam(); // ??? 값을 가져올 수 있는지 없는지 알수 없다.

      member.getOrder().getDelivery(); // ??? 값을 가져올 수 있는지 없는지 알수 없다.

   }

}

그래서 아래와 같이 DAO에서 동일한 조회 메서드를 여러벌 생성하여 해결하려고 한다.

모든 객체를 미리 로딩할 수는 없다. 

상황에 따라 동일한 조회 메서드를 여러벌 생성

 

memberDAO.getMember();                //Member만 조회

memberDAO.getMemberWithTeam();   //Member와 Team 조회

 

//Member, Order, Delivery

memberDAO.getMemberWithOrderWithDelivery();

 

하지만 객체 그래프 탐색 그림을 보듯이 경우의 수가 너무 많아진다.


비교하기

String memberId = "100";

Member member1 = memberDAO.getMember(memberId);

Member member2 = memberDAO.getMember(memberId);

 

member1 == member2;  //다르다.

 

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 = list.get(memberId);

Member member2 = list.get(memberId);

 

member1 == member2;  //같다.

 

위와 같이 DB에서 조회해서 가져온 member 객체는 다르고 자바 컬렉션에서 조회한 member 객체는 같다. 결국 객체지향적으로 모델링할수록 매핑 작업만 늘어난다. 

객체를 자바 컬렉션에 저장하듯이 DB에 저장할 수 없을까?

자바 진영에서는 JPA(Java Persistence(영구저장) API)가 나왔다. 

JPA?

- Java Persistence API

- 자바 진영의 ORM 기술 표준

ORM?

- Object-relational mapping(객체 관계 매핑)

- 객체는 객체대로 설계

- 관계형 데이터베이스는 관계형 데이터베이스대로 설계

- ORM 프레임워크가 중간에서 매핑 (개발자는 단순히 자바 컬렉션에서 조회하듯이 조회하고 저장하겠다.)

- 대중적인 언어에는 대부분 ORM 기술이 존재

JPA는 애플리케이션과 JDBC 사이에서 동작

JPA 동작 - 저장

JPA는 단순하게 패러다임의 불일치를 해결한다. 단순하게 객체를 DB로 바꾸는 정도가 아니다. "1. 객체상속 관계"에서 ITEM 테이블과 ALBUM 테이블에도 데이터를 넣기 위해 INSERT 쿼리를 2개를 작성했다. JPA 퍼시스턴스 앨범 객체에 넣으면 자동으로 쿼리를 2번 생성하여 넣는다. 단순히 DB에 넣는 것이 아니라 패러다임의 불일치 자체를 해결해준다. ALBUM 테이블 조회 시 JPA는 ALBUM 테이블과 ITEM 테이블을 조인해서 그 조인쿼리를 날려서 데이터를 가져오고 Album 객체를 반환한다. 이게 바로 핵심이다. JPA는 1. 중간에서 쿼리를 만들어주고 2. 패러다임의 불일치를 해결한다. 

JPA 동작 - 조회

조회시 JPA에게 find(id)로 find() 메서드에게 id 식별자를 넘겨주면 JPA는 패러다임의 불일치를 해결하고 적절한 쿼리를 생성하고 JDBC API를 사용하여 결과를 모두 매핑해서 반환한다. 마이바티스와 다른 점은 DTO 매핑을 해주지만 쿼리는 짜야한다. 하지만 JPA는 추가로 쿼리도 직접 작성해준다. 쉽게 애기하면 예전에 쿼리를 직접 개발했던 것을 기계(JPA)가 해주는 것이다. 

JPA 소개

예전에는 제대로 서버를 띄우기 위해서는 상용 WAS를 사용했다. 웹 로직등등. 이런 것들이 수천만원을 호가한다. 이런 데에 EJB라는 기능이 들어 있었다. EJB에는 엔티티 빈이라는 게 있는 데 이게 바로 JPA 초창기 기술이다. 이것을 자바 진영에서 표준화하여 기술을 만들었다. 금융권에 오래된 레거시를 손대지 않는 이상 만날 일은 없다. 문제는 EJB에 자바진영에서 표준적으로 내세운 ORM 기술이 있었는데 너무 코딩이 지저분했다. 서버를 띄우는 데도 몇 분씩 걸리고 개발도 인터페이스가 너무 복잡하고 성능도 너무 느렸다. 당시 해외 SI업계에 종사하고 있었던 게빈 킹이라는 개발자가 개발하기 너무 힘들어서 하이버네이트라는 기술을 개발했다. 게빈 킹의 사상에 공감하는 사람들이 많아지면서 오픈소스로 정식 출시했다. 그래서 다시 자바진영에서는 반성을 한다. 그래서 표준화하려고 한다. 표준화를 해야 표준화된 방식으로 여러 WAS들을 만들어서 오라클이나 웹로직 이런데서 WAS들을 만들어서 팔아먹을 수 있기 때문이다. 그래서 JPA라는 표준을 만든다. 이 때 중요한 것은 게빈 킹이라는 개발자를 데려와서 만든다. 그래서 거의 하이버네이트에 있는 기술을 ctrl +c, ctrl + v 해서 만든거라고 보면된다. JPA는 인터페이스만 있는 거라고 보면 된다. 구현체는 각 기업에서 하는 것이다. 그 중에서 무료 오픈소스인 하이버네이트가 있는 것이다. 상용 구현체도 있다. 아무튼 표준을 만드는데 표준이 거의 하이버네이트의 복붙이다. 표준화를 하면서 애매한 용어를 정리했다. 

JPA는 표준명세

- JPA는 인터페이스의 모음

- JPA 2.1 표준 명세를 구현한 3가지 구현체

- 하이버네이트, EclipseLink, DataNucleus

우리는 하이버네이트 구현체를 쓴다고 보면 된다. JPA는 인터페이스고 실질적인 구현체는 하이버네이트이다. 

JPA 버전

- JPA 1.0(JSR 220) 2006년 : 초기 버전. 복합 키와 연관관계 기능이 부족

- JPA 2.0(JSR 317) 2009년 : 대부분이 ORM 기능을 포함, JPA Criteria 추가

- JPA 2.1(JSR 338) 2013년 : 스토어드 프로시저 접근, 컨버터(Converter), 엔티티 그래프 기능이 추가.

 

EJB라는게 데이터 액세스를 하기 위한 기술도 있고 현재 스프링 프레임워크같은 컨테이너의 기능도 있었다. 하지만 이때 개발하기가 힘들었다. 그래서 로드존슨이라는 사람이 "EJB없이 개발하자"라는 책도 냈다. 바로 스프링 프레임워크를 만든 로드존슨이다. 게빈 킹, 로드 존슨 이 사람들은 다 SI 출신이다. 그래서 로드존슨이 만든 스프링 프레임워크와 게빈 킹이 만든 하이버네이트가 득세를 한다. 그러면서 스프링 프레임워크의 기술들을 자바진영에서 가져와서 표준화를 하고 있다. 해외에서는 이 두가지가 거의 기술표준이 된다. 호주나 미국에서는 스프링 프레임워크를 쓰면 JPA를 거의 쓴다고 보면 된다. 국내에서는 스프링 프레임워크를 쓰면서 마이바티스나 아이바티스를 쓴다. 마이바티스나 아이바티스는 객체를 매핑해주지만 쿼리는 직접 짜야 한다. 

JPA를 왜 사용해야 하는가? 

- SQL 중심적인 개발에서 객체 중심으로 개발

- 생산성

- 유지보수

- 패러다임의 불일치 해결

- 성능

- 데이터 접근 추상화와 벤더 독립성

- 표준

생산성 - JPA와 CRUD

  • 저장 : jpa.persist(member)  -> 한 줄이면 저장이 끝난다. 
  • 조회 : Member member = jpa.find(memeberId)
  • 수정 : member.setName("변경할 이름")
  • 삭제 : jpa.remove(member)

유지보수 - 기존 : 필드 변경시 모든 SQL 수정

쿼리를 짜지 않기 때문에 유지보수하기가 편하다. 단지 Member 클래스에 필드만 추가하면 된다. 

JPA와 패러다임의 불일치 해결

1. JPA와 상속

2. JPA와 연관관계

3. JPA와 객체 그래프 탐색

4. JPA와 비교하기

JPA와 상속 - 저장

개발자가 할 일 : jpa.persist(album);

나머진 JPA가 처리 : INSERT INTO ITEM ... / INSERT INTO ALBUM ...

JPA와 상속 - 조회

개발자가 할 일 : Album album = jpa.find(Album.class, albumId);

나머진 JPA가 처리 : SELECT I.*, A.*

                           FROM ITEM A

                             JOIN ALBUM A ON I.ITEM_ID = A.ITEM_ID

JPA와 연관관계, 객체 그래프 탐색

연관관계 저장(member가 팀을 변경할 경우(예를 들자면 처음 저장시 member와 team을 새로 만든다고 할 때) :                             member.setTeam(team);  jpa.persist(member); 코드 2줄이면 member와 team이 같이 저장된다.

객체 그래프 탐색(조회): Member member = jpa.find(Member.class, memberId); Team team = member.getTeam();

member만 가져왔는데 어떻게 team이 조회가 될까? 조인쿼리로 가져올 것 같지만 아니다. 바로 member.getTeam() 할때 팀이 조회가 안되어 있으면 이 때 조회를 한다. 정확하게는 team을 가져와서 team에 있는 정보를 사용할 때 아직 team 있는 정보가 조회가 안되어 있으면 이 때 team만 조회하는 쿼리를 날려서 가져와서 조회한다. 세팅부분에서 설정을 다르게 하면 member를 조회할 때 가져올 수 도 있다. 

신뢰할 수 있는 엔티티, 계층

class MemberSerive {

   ...

   public void process() {

      Member member = memberDAO.find(memberId);

      member.getTeam();  //자유로운 객체 그래프 탐색 (물론 성능에 대한 것은 본인이 고민해야 한다. 성능을 떠나서                                      //JPA 매커니즘이 이런 것을 지원한다.)

      member.getOrder().getDelivery(); 

   }

}

JPA와 비교하기

String memberId = "100";

Member member1 = jpa.find(Member.class, memberId);

Member member2 = jpa.find(Member.class, memberId);

 

member1 == member2; //같다. 

 

동일한 트랜잭션에서 조회한 엔티티는 같음을 보장(마치 자바 컬렉션에 넣고 빼는 것과 같다고 느낄 수 있다.)

자바 컬렉션에서 넣고 빼는 것과 같이하고 나머지는 JPA가 알아서 해준다. 이게 JPA의 컨셉이다. 

JPA의 성능 최적화 기능

1. 1차 캐시의 동일성(identity) 보장

2. 트랜잭션을 지원하는 쓰기 지연(transactional write-behind)

3. 지연 로딩(Lazy Loading) 

 

사실 JPA를 사용하면 성능이 느려지지 않을까하는 걱정이 있다. 성능이 느려질 수가 있다. 중간에 작업해주는 뭔가가 있으니 말이다. 하지만 반대로 중간에 뭔가가 있기 때문에 할 수 있는게 많다. 예로 같은 트랜잭션 안에서는 조회하면 2번째에서는 DB에서 가져오지 않고 JPA가 가지고 있는 1차 캐시에서 가져온다. 바로 JPA라는 중간레이어가 있어서 가능한 것이다. 

1차 캐시와 동일성 보장

1. 같은 트랜잭션 안에서는 같은 엔티티를 반환 - 약간의 조회 성능 향상

2. DB Isolation Level이 Read Commit이어도 애플리케이션이 Repeatable Read 보장

 

Sring memberId = "100";

Member m1 = jpa.find(Member.class, memberId); //SQL

Member m2 = jpa.find(Member.class, memberId); //캐시

 

println(m1 == m2); // true

 

SQL 한번만 실행

 

결론은 2개를 조회하게 되면 첫번째는 쿼리가 나간다. 두번째는 캐시에서 조회한다. 단, 같은 트랜잭션 안이어야 한다.

트랜잭션을 지원하는 쓰기 지연 - INSERT

1. 트랜잭션을 커밋할 때까지 INSERT SQL을 모음

2. JDBC BATCH SQL 기능을 사용해서 한번에 SQL 전송(중간에 버퍼를 쓸 수 있다.)

 

transaction.begin();   // [트랜잭션] 시작

 

em.persist(memberA);

em.persist(memberB);

em.persist(memberC);

//여기까지는 INSERT SQL을 데이터베이스에 보내지 않는다.

 

//커밋하는 순간 데이터베이스에 INSERT SQL을 모아서 보낸다.

transaction.commit(); // [트랜잭션] 커밋

지연로딩과 즉시로딩

  • 지연로딩 : 객체가 실제 사용될 때 로딩
  • 즉시로딩 : JOIN SQL로 한번에 연관된 객체까지 미리 조회

실제 개발을 할 때는 다 지연로딩으로 설정해놓고 실제 성능테스트에서 병목부분이 나오면 즉시로딩으로 바꿀 수 있다. 만약 위 상황과 같이 지연로딩에서 즉시로딩으로 변경할 때 현업에서 쿼리를 직접 짜야된다면 쿼리와 매핑을 다 다시 짜야 한다. 하지만 JPA에서는 설정을 바꾸는 것만으로 해결된다. 현대 애플리케이션은 훨씬 더 큰 그림을 보아야 한다. 전체적인 그림을 보고 성능테스트를 해봐야 한다. 그러고 나서 성능테스트를 통해 나온 데이터를 보고 의사결정을 해야한다. 

ORM은 객체와 RDB 두 기둥위에 있는 기술

결론은 객체도 잘 다뤄야하고 RDB도 잘 다뤄야 한다. 

'IT공부 > JPA' 카테고리의 다른 글

JPA 6강 - JPA 내부구조  (0) 2020.05.22
JPA 5강 - 양방향 매핑  (0) 2020.05.21
JPA 4강 - 연관관계 매핑  (0) 2020.05.21
JPA 3강 - 필드와 컬럼 매핑  (0) 2020.05.21
JPA 2강 - JPA기초와 매핑  (0) 2020.05.20