본문 바로가기
IT 관련기술

[JPA] JPA (Java Persistence API)

by bits 2022. 1. 6.

JPA 소개 및 JPA 기본 동작 과정

 

JPA는 자바 진영에서 ORM(Object-Relational Mapping) 기술 표준으로 사용되는 인터페이스의 모음이다. JPA를 구현한 대표적인 오픈소스로는 Hibernate 가 있다.

 

ORM(Object-relational mapping) 이란

  • Object-relational mapping (객체 관계 매핑)
  • 객체는 객체대로 설계하고, 관계형 데이터베이스는 관계형 데이터베이스대로 설계한다.
  • ORM 프레임워크가 중간에서 매핑해 준다.
  • 대중적인 언어에는 대부분 ORM 기술이 존재한다.
  • ORM은 객체와 RDB 두 기둥 위에 있는 기술이다.

JPA(Java Persistence API) 란

  • EJB

과거의 ORM 자바 표준(Entity Bean)

코드가 매우 지저분하다.

API 의 복잡성이 높다(interface를 많이 구현해야 함)

속도가 느리다.

 

  • JPA(Java Persistence API)

현재 자바 진영의 ORM 기술 표준으로 인터페이스의 모음이다.

JPA 인터페이스를 구현한 대표적인 오픈소스가 Hibernate라고 할 수 있다.

JPA 2.1 표준 명세를 구현한 구현체 : Hibernate, EclipseLink, DataNucleus, OpenJPA, TopLink Essentials 등이 있다.

  • Hibernate
  • Open Source ORM 프레임워크, 하이버네이트는 JPA 구현체의 한 종류이다.
  • 'Gavin King'과 시러스 테크놀로지스 출신 동료들이 EJB2 스타일의 Entity Beans 이용을 대체할 목적으로 개발하였다.
  • Hibernate 가 SQL을 직접 사용하지 않는다고 해서  JDBC API를 사용하지 않는다는 것은 아니다.
  • Hibernate 가 지원하는 메서드 내부에서는 JDBC API 가 동작하고 있으며, 단지 개발자가 직접 SQL을 직접 작성하지 않을 뿐이다.
  • HQL(Hibernate Query Language)이라 불리는 매우 강력한 쿼리 언어를 포함하고 있다.
  • HQL은 SQL과 매우 비슷하며 추가적인 컨벤션을 정의할 수 도 있다.
  • HQL은 완전히 객체 지향적이며 이로써 상속, 다형성, 관계등의 객체지향의 강점을 누릴 수 있다.
    HQL은 쿼리 결과로 객체를 반환하며 프로그래머에 의해 생성되고 직접적으로 접근할 수 있다.
    HQL은 SQL에서는 지원하지 않는 페이지네이션이나 동적 프로파일링과 같은 향상된 기능을 제공한다.
    HQL은 여러 테이블을 작업할 때 명시적인 join을 요구하지 않는다.

 

JPA의 동작 과정

JPA는 어플리케이션과 JDBC 사이에서 동작한다.

개발자가 JPA를 사용하면 JPA 내부에서 JDBC API 를 사용하여 SQL을 호출하여 DB와 통신한다.

 

저장 과정

MemberDAO에서 객체를 저장하고 싶을 때 개발자는 JPA에 Member 객체를 넘기면 JPA는 아래와 같이 동작한다.

1) Member 엔티티를 분석한다.

2) INSERT SQL을 생성한다.

3) JDBC API를 사용하여 SQL을 DB 에 날린다.

 

조회 과정

Member 객체를 조회하고 싶을 때 개발자는 member의 pk 값을 JPA에 넘긴다.

1) 엔티티의 매핑 정보를 바탕으로 적절한 SELECT SQL을 생성한다.

2) JDBC API를 사용하여 SQL을 DB에 날린다.

3) DB로 부터 결과를 받아온다.

4) 결과(ResultSet)를 객체에 매핑한다.

쿼리를 JPA가 만들어 주기 때문에 Object와 RDB간의 패러다임 불일치를 해결 할 수 있다.

 

JPA를 왜 사용해야 하는가?

1. SQL 중심적인 개발에서 객체 중심으로 개발로의 패러다임 변환

2. 생산성 향상

JPA를 사용하는 것은 마치 Java Collection에서 데이터를 넣었다 빼는 것처럼 사용할 수 있게 만든 것이다.

간단한 CRUD

더보기

저장 : jpa.persist(member)

조회 : Member member = jpa.find(memberId)

수정 : member.setName("홍길동")

삭제 : jpa.remove(member)

3. 유지보수

기존 SQL 방식 : 테이블 필드 변경시 모든 SQL을 수정해야 한다.

JPA 방식 : Entity 객체에 필드만 추가하면 된다.

 

4. Object 와 RDB 간의 패러다임 불일치 해결

1) JPA와 상속

  • 저장

jpa.persist(album) : album 객체를 저장하면 JPA가 ITEM, ALBUM 테이블에 INSERT 쿼리가 수행된다.

JPA 처리 : INSERT INTO ITEM ...

INSERT INTO ALBUM ...

 

  • 조회

Album album = jpa.find(Album.class, albumId); : find()를 통해 조회는 하면 JPA에 의해 아래의 쿼리가 수행된다.

JPA 처리 : SELECT I.*, A.* FROM ITEM I JOIN ALBUM A ON I.ITEM_ID = A.ITEM_ID

 

2) JPA와 연관관계

객체관계와 이를 테이블 구조로 나타낸 아래의 그림을 보도록 하자.

출처 : 자바 ORM 표준 프로그래밍

위의 그림을 코드로 나타내면 아래와 같다.

class Member{
	String id;
    String username;
    Team team;
}

class Team{
	Long id;
    String name;
}

 

객체의 참조로 연관관계 저장 가능

member.setTeam(team);

jpa.persist(member);

*****

3) JPA와 객체 그래프 탐색

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

class MemberService {
	...
    public void process(){
    	// 기존 방식처럼 직접 구현한 DAO에서 객체를 가져온 경우
    	Member member1 = memberDAO.find(memberId);
        member1.getTeam(); // 엔티티를 신뢰할 수 없음
        member1.getOrder().getDelivery();
        
        // JPA를 통해서 객체를 가져온 경우
        Member member2 = jpa.find(Member.class, memberId);
        member2.getTeam(); // 자유로운 객체 그래프 탐색
        member2.getOrder().getDelivery();    
    }
}
  • 내가 아닌 다른 개발자가 직접 구현한 DAO에서 가져오는 경우

DAO에서 직접 어떤 쿼리를 날렸는지 확인하지 않는 이상, 그래프 형태의 관련된 객체들을 모두 잘 가져왔는지 알 수 가 없다.

즉, 반환한 엔티티를 신뢰하고 사용할 수 없다.

 

  • JPA를 통해서 가져오는 경우

객체 그래프를 완전히 자유롭게 탐색할 수 있게 된다.

지연 로딩 전략(Lazy Loading) : 관련된 객체를 사용하는 그 시점에 SELECT Query 를 날려서 객체를 가져오는 전략 사용

 

4) JPA와 비교하기

동일한 트랜잭션에서 조회한 엔티티는 같음을 보장한다.

String memberId = "100";
Member member1 = jpa.find(Member.class, memberId);  // DB에서 가져옴
Member member2 = jpa.find(Member.class, memberId);  // 1차 캐시에서 가져옴
member1 == member2;  // 같다

 

5. JPA의 성능 최적화 기능 *

중간 계층이 있는 경우 아래의 방법으로 성능을 개선할 수 있는 기능이 존재한다.

  • 모아서 쓰는 버퍼링 기능
  • 읽을 때 쓰는 캐싱 기능

JPA 도 JDBC API와 DB 사이에 존재하기 때문에 위의 두 기능이 존재한다.

 

1) 1차 캐시와 동일성(identity) 보장 - 캐싱 기능

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

String memberId = "100";
Member member1 = jpa.find(Member.class, memberId);  // DB에서 가져옴
Member member2 = jpa.find(Member.class, memberId);  // 1차 캐시에서 가져옴
member1 == member2;  // 같다

동일 조건인 경우 쿼리를 2번 실행하지 않고, 캐시에서 읽어들임.

DB Isolation Level 이 Read Commit 이어도 어플리케이션에서 Repeatable Read 보장

 

2) 트랜잭션을 지원하는 쓰기 지연 (transactional write-behind) - 버퍼링 기능

  • INSERT
// 1. 트랜잭션을 커밋할 때까지 INSERT 쿼리를 수행하지 않음
transaction.begin();	// 트랜잭션 시작
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);

// 2. 커밋하는 순간 데이터베이스에 INSERT 쿼리를 일괄 처리한다.
// JDBC BATCH SQL 기능을 사용해서 한번에 SQL 전송
transaction.commit();	// 트랜잭션 커밋
  • UPDATE
// 1. UPDATE, DELETE로 인한 로우 락(ROW LOCK) 시간 최소화
transaction.begin();	// 트랜잭션 시작
changeMember(memberA);
deleteMember(memberB);

비즈니스_로직_수행();	// 비즈니스 로직 수행 동안 DB 로우 락이 걸리지 않는다.

// 커밋하는 순간 데이터베이스에 UPDATE, DELETE 쿼리를 보내고 바로 커밋
transaction.commit();	// 트랜잭션 커밋

 

3) 지연 로딩(Lazy Loading)

  • 지연 로딩

객체가 실제로 사용될 때 로딩하는 전략

Member member = memberDAO.find(memberId);    // SELECT * FROM MEMBER
Team team = member.getTeam();
String teamName = team.getName();    // SELECT * FROM TEAM

memberDAO.find(memberId) 에서 Member 객체에 대한 SELECT 쿼리만 날린다.

Team team = member.getTeam()로 Team 객체를 가져온 후에 team.getName() 처럼 실제로 Team 객체를 참조하는 시점에 JPA가 Team 에 대한 SELECT 쿼리를 수행한다.

 

  • 즉시 로딩

JOIN 쿼리로 한번에 연관된 객체까지 미리 조회하는 전략

Member member = memberDAO.find(memberId);  // SELECT M.*, T.* FROM MEMBER JOIN TEAM ...
Team team = member.getTeam();
String teamName = team.getName();
  • 어플리케이션 개발시 기본적으로 지연 로딩을 설정한 후 필요한 경우에 즉시 로딩으로 옵션을 변경하는 것을 추천한다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


[참고]

https://gmlwjd9405.github.io/2019/08/04/what-is-jpa.html

https://velog.io/@adam2/JPA%EB%8A%94-%EB%8F%84%EB%8D%B0%EC%B2%B4-%EB%AD%98%EA%B9%8C-orm-%EC%98%81%EC%86%8D%EC%84%B1-hibernate-spring-data-jpa

 

 

 

 

 

 

 

 

댓글