본문 바로가기
Back-end/JPA 개념

1. JPA의 등장

by devraphy 2022. 3. 22.

0. 개요

- 이전 포스팅에서 JPA를 사용하는 이유에 대해서 알아보았다.

- 왜 JPA가 필요한지, 근본적인 문제점이 무엇인지에 대해서 알아보았다.

 

- 복습하자면 JPA를 사용하는 이유는 SQL 중심의 개발 방식을 객체지향 중심의 개발 방식으로 개선하기 위함이다.

- 그렇다면 JPA가 무엇인지 천천히 알아보자.

 

1. JPA란?

- JPA는 Java Persistent API의 축약어다.

- 쉽게 설명하자면 JAVA의 ORM 표준 기술을 의미한다.

 

a) ORM(Object-relational Mapping)

- ORM이 의미하는 바, 역할하는 바는 JPA가 필요한 이유와 동일하다.

 

- JPA가 필요한 이유는 OOP와 RDB의 구조적 차이로 인해 데이터를 매핑하는 과정에 있어서

   SQL 중점적인 개발 방식을 사용하게 된다는 부분에서 문제에서 시작한다.

 

- ORM을 사용하면 개발자는 OOP로 개발하고,  RDB는 RDB 답게 설계하여 사용할 수 있다.

- ORM은 OOP와 RDB 사이에서 중재자의 역할을 하는 것이다.

 

- 즉, ORM은 OOP와 RDB의 구조적 차이로 인해 발생하는 데이터 매핑 과정의 문제를 해결한다. 

 

* 오해하지 말자!

- ORM은 Java에서만 사용되는 특별한 기술이 아니다. 

- 객체지향을 사용하는 모든 프로젝트에 적용할 수 있는 개념이다.

- 그러므로 대부분의 대중적인 프로그래밍 언어는 ORM을 지원한다. ex) Java, Python, TypeScript

 

 

b) JPA의 구조적 위치

- JPA는 Java application과 JDBC 사이에 위치한다.

 

- 개발자 또는 application이 JDBC와 직접 소통하는 것이 아닌, JPA를 거쳐서 소통하는 방식으로 구조를 이룬다.

- 즉, application에서 특정 객체와 명령을 JPA에게 넘기면 JPA는 이를 분석 및 해석하여 SQL을 대신 작성한다.

 

- 이렇게 작성된 SQL은 JDBC에게 전달되고, JDBC는 이를 이용하여 DB에게 명령을 하달한다.

- 그렇다면 JPA는 중간에서 어떻게 OOP와 RDB의 문제점을 해결하는 것일까?

- 이에 대해서는 나중에 다시 언급하도록 하겠다.

 

c) JPA의 구조

- JPA는 특별한 동작을 하는 프로그램이 아니라 인터페이스의 모음이다.

- JPA의 대표적인 구현체로 Hibernate, EclipseLink, DataNucleus가 있다.

 

2. JPA를 사용하는 이유(1) - 생산성

- JPA를 사용하는 이유와 JPA 사용의 장점에 대해서 간단하게 알아보자.

 

a) 우수한 생산성 - CRUD

- JPA에는 인터페이스로 CRUD 동작이 메서드로 정의되어 있다.

    → 저장(create): jpa.persist(객체)

    → 조회(read): Object object = jpa.find(객체 속성 값)

    → 수정(update): object.setName(변경 값)

    → 삭제(delete): jpa.remove(객체)

 

3. JPA를 사용하는 이유(2) - 유지보수

 

a) 우수한 유지보수 

- 객체(DAO)에 새로운 필드가 추가되면 JPA가 알아서 해당 필드를 추가하여 SQL을 작성한다.

- 그러므로 따로 SQL을 고칠 필요도 고민할 필요도 없다.

- 즉, 개발자는 객체지향대로 개발하면 된다.

 

4. JPA를 사용하는 이유(3) - 구조적 차이 해결

- JPA는 OOP와 RDB의 구조적 차이를 해결(= 패러다임 불일치의 해결)

- 그렇다면 어떤 방식으로 패러다임의 불일치를 해결하는지 알아보자. 

 

a) 상속관계 처리

- OOP의 상속관계를 RDB는 FK를 통한 참조로 구현한다. 즉, 서로 다른 메커니즘으로 동작하게 된다.

- 저장(create) 작업을 하는 경우, 부모와 자식 클래스에 해당하는 각 테이블에 SQL을 따로따로 적용해야 한다.

- JPA는 상속관계를 이해하고 이에 알맞은 SQL을 작성한다.

 

b) 연관관계 처리

- 어떤 클래스 A 내부에 클래스 B의 객체를 값으로 가지는 관계를 연관관계라고 말한다.

- 이와 같은 개념은 OOP에는 존재하지만, 마찬가지로 RDB에는 존재하지 않는 개념이다.

 

- 만약 연관관계에 있는 값을 조회(read)하는 경우, 클래스 A에 해당하는 테이블 A를 조회하고

   클래스 B에 해당하는 테이블 B를 조회하여 값을 찾아야 한다.

 

- JPA는 연관관계를 이해하고 이에 알맞은 SQL을 작성한다. 

 

c) 객체 그래프 탐색 처리

- RDB의 구조와는 상관없이 OOP의 객체 그대로를 탐색 및 사용할 수 있다.

 

- 클래스 A의 객체 내부에 클래스 B의 객체를 값으로 보유하고 있는 경우,

   RDB에서는 테이블 A(= 클래스 A)와 B(= 클래스 B) 각각에서 데이터를 조회해야 한다.

 

- 그러나 JPA가 이를 알아서 처리해주기 때문에, 개발자는 이에 상관없이 OOP대로 개발하면 된다.

 

d) 객체 비교 처리 

- 다음 예시를 살펴보자.

int userId = 100;

User userA = jpa.find(User.class, userId);
User userB = jpa.find(User.class, userId);

userA == userB;

 - 위의 코드에서 userA와 userB를 비교하는 경우, 서로 다른 객체라고 생각할 것이다.

- 그러나 JPA를 사용하는 경우, 동일한 트랜젝션에서 조회한 Entity는 동일한 객체임을 보장한다.

- 즉, userA와 userB를 동일한 객체로 판단한다. 이는 아래에서 설명할 JPA의 캐싱 기능 때문이다.

 

e) 성능 최적화

- JPA를 사용하면 오히려 응답 속도가 느려지는 게 아닐까? 생각할 수 있다.

- 그러나 JPA의 다음 기능을 사용하여 성능 최적화를 이뤄낼 수 있다.

 

1. 캐시와 동일성(Identity) 보장

   → 동일한 SQL을 중복적으로 사용하는 경우, 이에 대한 결과 값을 캐시에 저장한다. 

   → 즉, 매번 SQL을 DB에게 전달하여 결과 값을 반환받는 것이 아니라 동일한 명령에 대한 결과 값을 저장하여 사용한다.

   → 그러므로 응답 속도가 더욱 빠르다. 

 

* 여기서 말하는 캐시는 일반적으로 메모리에서 사용되는 캐시를 말하는 것이 아니다. 

   고객의 요청에 의해 수행되는 한 번의 SQL 트랜잭션 사이클 내에서 동일한 SQL 명령에 대해서 말하는 것이다. 

   그러므로 굉장히 짧은 Lifecycle을 가지는 캐시다. 

 

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

 - 동일한 작업에 대해서 일괄적으로 처리하는 기능을 말한다.

 

 - 예를 들어, 특정 데이터를 저장하는 기능을 3번 연속으로 요청한다고 해보자.

 - 이를 따로따로 처리하는 경우, 3번의 네트워킹이 발생하게 된다. 

 

 - 즉, 동일한 기능을 처리하기 위해 3번의 연산이 발생하고, 그만큼 시간이 더 오래 걸린다. 

 - 그러므로 트랜잭션을 단일 처리하지 않고, 모아서 일괄 처리한다.

 

3. 지연 로딩(Lazy Loading)

 - 지연 로딩이란, 객체가 사용되는 시점에 SQL이 수행되는 것을 말한다.

 - 이는 연관관계를 가지는 객체를 사용할 때 효율적이다. 

 

 - 예를 들어, 클래스 A 내부에 클래스 B의 객체를 값으로 가지고 있다고 해보자.

 

 - 클래스 A의 구현체인 객체 A의 값을 조회한 후 객체 A가 갖고 있는 클래스 B의 객체 값을 조회하는 경우

     우선 객체 A의 값을 조회할 때 SQL 트랜잭션이 한번 발생한다. 

 

 - 그리고 객체 A가 가지고 있는 클래스 B의 값을 조회할 때 SQL 트랜잭션이 한번 더 발생한다. 

 

 - 이와 같이 처리하는 방식을 지연 로딩이라고 한다.

 

  → 다음 코드를 살펴보면 이해가 쉬울 것이다. 

class User {
   int userId;
   String userName;
   Team team;
}

class Team {
   Sring teamName;
}

User userA = userDAO.find(userId); // => SELECT * FROM USER
Team team = userA.getTeam();
String teamName = team.getTeamName(); // => SELECT * FROM TEAM

 

- 지연 로딩을 항상 사용하는 것은 비효율적이다.

- 그러므로 프로그램의 흐름에 따라서 지연 로딩의 사용 여부를 옵션을 통해 설정하는 것을 권장한다.

 

- 위의 코드를 예시로 들자면 다음과 같은 상황이다.

   → User 객체를 조회할 때 반드시 Team 값을 조회하는 경우, 트랜잭션을 두 번 수행할 필요가 없다.

   → 오히려 모든 트랜잭션을 받아서 다음과 같이 트랜잭션을 한 번에 수행하는 것이 효율적이다. 

   → 이와 같은 조회 방식을 즉시 로딩이라고 한다. 

User userA = userDAO.find(userId); 
Team team = userA.getTeam();
String teamName = team.getTeamName();
// => SELETC USER.*, TEAM.* FROM USER JOIN TEAM...

'Back-end > JPA 개념' 카테고리의 다른 글

5. Field(칼럼) Mapping  (0) 2022.03.28
4. Entity Mapping  (0) 2022.03.25
3. JPA의 내부구조와 동작  (0) 2022.03.24
2. JPA의 기본 구조와 기능  (0) 2022.03.23
0. JPA를 사용하는 이유  (0) 2022.03.21

댓글