JPA

호돌·2021년 1월 12일
0

BackEnd

목록 보기
9/25
post-custom-banner

🤔JPA란?(Java Persistence API)

JAP란, Java Persistence API (자바 ORM 기술에 대한 API 표준 명세)로 ORM을 사용하기 위한 인터페이스를 모아둔 것이라고 볼 수 있다. 간단하게 말해 Persistence(영속성) 즉, 자바 어플리케이션을 이용해 관계형 데이터베이스에 데이터를 프로그램이 종료되더라도 사라지지 않게 기록하고 사용하는 방식을 정의한 인터페이스이다.

API?(Application Programming Interface)
응용 프로그램에서 사용할 수 있도록, 운영 체제나 프로그래밍 언어가 제공하는 기능을 제어할 수 있게 만든 인터페이스를 뜻한다. 주로 파일 제어, 창 제어, 화상 처리, 문자 제어 등을 위한 인터페이스를 제공한다.즉, 프로그래밍의 방법을 통해 어플리케이션(프로그램)을 만들 수 있게 제공해주는 인터페이스(상하관계가 있는 약속 ↔ 프로토콜)이다.

여기서 중요하게 여겨야 할 부분은, JPA는 말 그래도 인터페이스라는 점이다. JPA는 특정 기능을 하는 라이브러리가 아니다. 스프링의 PSA에 의해서(POJO를 사용하면서 특정 기술을 사용하기 위해서)표준 인터페이스를 정해두었는데, 그중 ORM을 사용하기 위해 만든 인터페이스가 바로 JPA이다.

결국 인터페이스이기 때문에 JPA를 사용하기 위해서는 JPA를 구현한 Hibernate, EclipseLink, DataNucleus 같은 ORM 프레임워크를 사용해야 한다.

장점

  • CRUD SQL을 작성할 필요가 없다.
  • 조회된 결과를 객체로 매핑하는 작업도 대부분 자동으로 처리.
  • 데이터 저장 계층에서 작성해야 할 코드가 대폭 줄어듬.
  • SQL이 아닌 객체 중심으로 개발하며 생산성과 유지보수가 좋아짐.
  • 데이타베이스 변경시 코드를 거의 수정하지 손쉽게 변경.

🙋‍♀️ ORM은 뭐죠?

Object-Relational Mapping(객체와 관계형데이터베이스 매핑, 객체와 DB의 테이블이 매핑을 이루 것) 즉, 객체가 테이블이 되도록 매핑 시켜주는 프레임워크이다.

기존 데이터의 흐름은 DML을 통해서 team테이블에 데이터를 저장해주고 SELECT문을 통해 Team 클래스와 같은 형태의 객체를 만들어야 합니다.

Team테이블
IDint
Namevarchar
Yearvarchar
class Team {
	int id;
    	string name;
    	string year;
}

하지만, ORM은 Object 즉, 아래와 같은 객체를 가지고 테이블을 생성할 수 있습니다.

class Team {
	int id;
    	string name;
    	string year;
}
Team테이블
IDint
Namevarchar
Yearvarchar

ORM은 프로그램의 복잡도를 줄이고 자바객체와 쿼리를 분리할 수 있으며 트랜잭션 처리나 기타 데이터베이스 관련 작업들을 좀 더 편리하게 처리할 수 있는 방법입니다.

SQL Query가 아닌 직관적인 코드(메서드)로서 데이터를 조작할 수 있 수 있는 특징이 있습니다.

SQL Mapper

  • SQL ←mapping→ Object 필드
  • SQL문으로 직접 디비를 조작한다.
  • Mybatis,jdbcTemplate

ORM(Object-Relation Mapping/객체-관계 매핑)

  • DB 데이터 ←mapping→ Object 필드

    • SQL쿼리가 아니라 메서드로 데이터를 조작할 수 있다.
  • 객체와 디비의 데이터를 자동으로 매핑해준다.

    • 객체간 관계를 바탕으로 sql을 자동으로 생성한다.
    • SQL쿼리가 아니라 메서드로 데이터를 조작할 수 있다.
  • Persistant API라고 할 수 있다.

  • JPA, Hibernate

JDBC


JDBC는 DB에 접근할 수 있도록 자바에서 제공하는 API이다.

모든 JAVA Data Access 기술의 근간이다. => 모든 Persistance Framework는 내부적으로 JDBC API를 이용한다.

🧩 SQL을 직접 다룰 때 발생하는 문제점

데이터베이스에 데이터를 관리하려면 SQL을 사용
자바로 작성한 애플리케이션은 JDBC API를 사용

1. 반복, 반복 그리고 반복

회원 등록 기능
회원 등록 기능 추가

public class MemberDAO {

    public Member find(String memberId){...}
    public void save(Member member){...} //추가
}
  1. 회원등록용 SQL 작성
String sql = "INSERT INTO MEMBER(MEMBER_ID, NAME) VALUES(?,?)";
  1. 회원 객체의 값을 꺼내서 등록 SQL에 전달
pstmt.setString(1, member.getMemberId());
pstmt.setString(2, member.getName());
  1. JDBC API를 사용해서 SQL을 실행
pstmt.executeUpdate(sql);

2. SQL에 의존적인 개발

만약 완성 후에 회원의 연락처도 함께 저장 해달라는 요구사항 추가가 이루어졌을 경우
등록 코드 변경 -> 조회 코드 변경 -> 수정 코드 변경 의 과정을 다시 반복해야 하게된다.

  • 진정한 의미의 계층 분할이 어렵다.
  • 엔티티를 신뢰할 수 없다.
  • SQL에 의존적인 개발을 피하기 어렵다.

🤪그럼 Spring-Data-JPA 같은건 뭐죠?

JPA는 ORM을 위한 자바 EE 표준이며 Spring-Data-JPA는 JPA를 쉽게 사용하기 위해 스프링에서 제공하고 있는 프레임워크이다.

추상화 정도는 Spring-Data-JPA -> Hibernate -> JPA이다

Hibernate를 쓰는 것과 Spring-Data-JPA를 쓰는 것 사이에는 큰 차이가 없지만

  • 구현체 교체의 용이성
  • 저장소 교체의 용이성

등과 같은 이유에서 Spring-Data-JPA를 사용하는것이 더 좋다.

📝JPA 동작 과정

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

개발자가 JPA를 사용하면, JPA 내부에서 JDBC API를 사용하여 SQL을 호출하고 DB와 통신한다.
즉, 개발자가 직접 JDBC API를 쓰는 것이 아니다.

저장 과정

MemberDAO에서 객체를 저장하고 싶을 때

  1. 개발자는 JPA에 Member 객체를 넘긴다.
  2. JPA는 Member 엔티티를 분석한다.
  3. INSERT SQL을 생성한다.
  4. JDBC API를 사용하여 SQL을 DB에 날린다.

조회 과정

Member 객체를 조회하고 싶을 때

  1. 개발자는 member의 PK 값을 JPA에 넘긴다.
  2. JPA는 엔티티의 매핑 정보를 바탕으로 적절한 SELECT SQL을 생성한다.
  3. DBC API를 사용하여 SQL을 DB에 날린다.
  4. DB로부터 결과를 받아온다.
  5. 결과(ResultSet)를 객체에 모두 매핑한다.

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

🙄JPA를 왜 사용해야 할까?

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

  • 지루한 코드의 무한반복
  • 객체지향과 관계형 데이터베이스 간의 패러다임 불일치
  • 객체와 관계형 데이터베이스의 차이
  • 모델링 과정에서의 문제
  • 객체 그래프 탐색에서의 문제

생산성

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

  • 간단한 CRUD

    • 특히 수정이 굉장히 간단하다. 객체를 변경하면 그냥 알아서 DB에 UPDATE Query가 나간다.

유지보수

  • 기존 : 필드 변경시 모든 SQL을 수정해야 한다.
  • JPA : 필드만 추가하면 된다. SQL은 JPA가 처리하기 때문에 손댈 것이 없다.

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

  • JPA와 상속
  • JPA와 연관관계
  • JPA와 객체 그래프 탐색

JPA와 문제 해결

JPA는 SQL 사용시 위의 문제를 어떻게 해결하는가?
JPA 사용 => 개발자가 직접 SQL을 작성하는 것이 아니라 JPA가 제공하는 API를 사용하면 된다.

저장 기능

persist() 메소드는 객체를 데이터베이스에 저장

jpa.persist(member); // 저장
  • JPA가 객체와 매핑 정보를 보고 적잘한 INSERT SQL 생성
  • 데이터베이스에 전달

조회기능

find()메소드는 객체 하나를 데이터베이스에서 조회

  • JPA는 객체와 매핑정보를 보고 적절한 SELECT SQL을 생성
  • 데이터베이스에 전달
  • 결과로 Member 객체를 생성해서 반환
String memberId = "helloId";
Member member = jpa.find(Member.class, memberId); // 조회

수정기능

JAP는 별도로 수정 메소드를 제공하지 않는다.
대신 객체를 조회해서 값을 변경만 하면 트랜잭션 커밋할 때 데이터베이스에 적절한 UPDATE SQL이 전달된다.

Member member = jpa.find(Member.class, memberId);
member.setName("이름변경") // 수정

연관된 객체 조회

JPA는 연관된 객체를 사용하는 시점에 적절한 SELECT SQL을 실행

Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam(); // 연관된 객체 조회

🤪 패러다임의 불일치

어플리케이션이 발전하면서 프로그램 내부의 복잡성은 점점 커지고 있다.
객체지향 프로그래밍은 추상화, 캡슐화, 정보은닉, 상속, 다형성 등 시스템의 복잡성을 제어할 수 있는 다양한 장치들을 제공한다.
관계형 데이터베이스는 데이터 중심으로 구조화, 집합적인 사고 필요, 추상화, 상속 다형성 같은 개념이 없다.
이런 개념들을 객체와 관계형 데이터베이스의 패러다임 불일치라고 하며 이러한 패러다임 불일치의 문제를 해결하는데 많은 시간과 코드를 소비한다.

1. 상속

객체는 상속이라는 기능을 가지고 있지만 테이블은 상속이라는 기능이 없다.

객체 상속 모델

테이블 설계

객체 모델 코드

abstract class Item {
    Long id;
    String name;
    int price;
}

class Album extends Item {
    String artist;
}

class Movie extends Item {
    String director;
    String actor;
}

class Book extends Item {
    String author;
    String isbn;
}

관련 SQL

-- Album 객체 저장
INSERT INTO ITEM ...
INSERT INTO ALBUM ...


-- Movie 객체 저장
INSERT INTO ITEM ...
INSERT INTO MOVIE ...

관련 처리
1. 부모 객체에서 부모 데이터만 꺼냄
2. Item용 INSERT SQL 작성
3. 자식 객체에서 자식 데이터만 꺼내서 ALBUM INSER SQL작성
4. (조회)ITEM과 ALBUM 테이블을 조인해서 그 결과를 다시 Album 객체 생성

만약 컬렉션 스타일로 한다면?

list.add(album);
list.add(movie);

Album album = list.get(albumId);

🐱‍🏍JPA와 상속
JPA는 상속과 관련한 패러다임의 불일치 문제를 개발자 대신 해결
자바 컬렉션에 객체를 저장하듯이 JPA에게 객체를 저장.

JPA 저장

jpa.persist(album);

JPA는 다음 SQL을 실행해서 객체를 ITEM, ALBUM 두 테이블에 나누어 저장

INSERT INTO ITEM ...
INSERT INTO ALBUM ...

JPA 조회

String albumId = "id100";
Album album = jpa.find(Album.class, albumId);

JPA는 ITEM과 ALBUM 두 테이블을 조인해서 필요한 데이터를 조회하고 결과를 반환

SELECT I.*, A.*
    FROM ITEM I
    JOIN ALBUM A ON I.ITEM_ID = A.ITEM_ID

2. 연관 관계

  • 객체는 참조를 사용해서 다른 객체와 연관관계를 가지고 참조에 접근해서 연관된 객체를 조회
  • 테이블은 외래 키를 사용해서 다른 테이블과 연관관계를 가지고 조인을 사용해서 연관된 테이블을 조회

객체를 테이블에 맞춰 모델링

  • 관계형 데이터베이스 방식에 맞추면 Member 객체와 연관된 Team 객체를 참조를 통해서 조회할 수 없다.
  • 좋은 객체 모델링은 기대하기 어렵고 결국 객체지향의 특징을 잃어버린다.

객체 지향 모델링
객체지향 모델링을 사용하면 객체를 테이블에 저장하거나 조회하기는 쉽지 않다.
객체 모델은 외래 키가 필요없고 단지 참조만 있음면 된다.
테이블은 참조가 필요 없고 외래키만 있으면 된다.
결국, 개발자가 중간에 변환 역할을 해야한다.

// 참조를 사용하는 객체 모델
class Member {
    String id;
    Team team;          // 참조로 연관관계를 맺는다.
    String username;

    Team getTeam() {
        return team;
    }
}

class Team {
    Long id;
    String name;
}

JPA와 연관관계
JPA는 연관관계와 관련한 패러다임 불일치 문제를 해결해준다.

member.setTeam(team); //회원과 팀 연관관계 설정
jpa.persis(member); //회원과 연관관계 함께 저장

객체를 조회할 떄 외래 키를 참조로 변환하는 일도 JPA가 처리

Member member = jpa.find(Member.class,memberId);
Team team = member.getTeam();

3. 객체 그래프 탐색

객체에서 회원이 소속된 팀을 조회할 떄 참조를 사용해서 연관된 팀을 찾으면 되는데 이것을 "객체 그래프 탐색" 이라고 한다.

member.getOrder().getOrderItem().... // 자유로운 객체 그래프 탐색

지연로딩 사용

투명한 엔티티

4. 비교

📝 영속성(Persistence)

데이터를 생성한 프로그램이 종료되더라도 사라지지 않는 데이터의 특성을 말한다. 영속석을 갖지 않는 데이터는 단지 메모리에서만 존재하기 때문에 프로그램을 종료하면 모두 잃어버리게 된다. 떄문에 파일 시스템, 관계형 데이터베이스 혹은 객체 데이터베이스 등을 활용하여 데이터를 영구하게 저장하여 영속성을 부여한다.

Persistence Layer
프로그램의 아키텍처에서, 데이터에 영속성을 부여해주는 계층을 말한다.
JDBC를 이용하여 직접 구현할 수 있지만 Persistence framework를 이용한 개발이 많이 이루어진다.

계층 참고
프레젠테이션 계층 (Presentation layer) - UI 계층 (UI layer) 이라고도 함
애플리케이션 계층 (Application layer) - 서비스 계층 (Service layer) 이라고도 함
비즈니스 논리 계층 (Business logic layer) - 도메인 계층 (Domain layer) 이라고도 함
데이터 접근 계층 (Data access layer) - 영속 계층 (Persistence layer) 이라고도 함

Persistence Framework
JDBC 프로그래밍의 복잡함이나 번거로움 없이 간단한 작업만으로 데이터베이스와 연동되는 시스템을 빠르게 개발할 수 있으며 안정적인 구동을 보장한다.

Persistence Framework는 SQL Mapper와 ORM으로 나눌 수 있다.
Ex) JPA, Hibernate, Mybatis 등

JPA 하이버네이트(Hibernate)

Hibernate는 JPA의 추상화 객체의 구현체 중 하나이다.

HQL(Hibernate Query Language)이라 불리는 매우 강력한 쿼리 언어를 포함하고 있다.

HQL의 특징

  • HQL은 SQL과 매우 비슷하며 추가적인 컨벤션을 정의할 수도 있다.
  • HQL은 완전히 객체 지향적이며 이로써 상속, 다형성, 관계등의 객체지향의 강점을 누릴 수 있다.
  • HQL 쿼리는 자바 클래스와 프로퍼티의 이름을 제외하고는 대소문자를 구분한다.
  • HQL은 쿼리 결과로 객체를 반환하며 프로그래머에 의해 생성되고 직접적으로 접근할 수 있다.
  • HQL은 SQL에서는 지원하지 않는 페이지네이션이나 동적 프로파일링과 같은 향상된 기능을 제공한다.
  • HQL은 여러 테이블을 작업할 때 명시적인 join을 요구하지 않는다.

Hibernate의 장점

  • 객체지향적으로 데이터를 관리할 수 있기 때문에 비지니스 로직에 집중 할 수 있으며, 객체지향 개발이 가능하다.
  • 테이블 생성, 변경, 관리가 쉽다(JPA를 잘 이해하고 있는 경우)
  • 로직을 쿼리에 집중하기 보다는 객체 자체에 집중 할 수 있다.
  • 빠른 개발이 가능하다.

Hibernate의 단점

  • 어렵다 (많은 내용이 감싸져 있기 떄문에 알아야 할 것이 많다.)
  • 잘 이해하고 사용하지 않으면 데이터 손실이 있을 수 있다.(Persistence Context)
  • 성능상 문제가 있을 수 있다.

🔨 JPA에서의 영속성


JPA의 핵심 내용은 엔티티가 영속성 컨텍스트에 포함되어 있냐 아니냐로 갈린다. JPA의 엔티디 매니저가 활성화된 상태로 트랜잭션(@Transactional) 안에서 DB에서 데이터를 가져오면 이 데이터는 영속성 컨텍스타가 유지된 상태이다. 이 상태에서 프로그램(자바)측에서 해당 데이터 값을 변경하면 트랜잭션이 끝나는 시점에 해당 테이블에 변경 내용을 반영하게 된다. 따라서 우리는 엔티티 객체의 필드 값만 변경해주면 별도로 update()쿼리를 날릴 필요가 없게 된다! 이 개념을 더티체킹이라고 한다.

Spring Data Jpa를 사용하면 기본으로 엔티티 매니저가 활성화되어있는 상태이다.

영속 컨텍스트: 엔티티를 담고 있는 집합. JPA는 영속 컨텍스트에 속한 엔티티를 DB에 반영한다. 엔티티를 검색, 삭제, 추가 하게 되면 영속 컨텍스트의 내용이 DB에 반영된다.

영속 컨텍스트는 직접 접근이 불가능하고 Entity Manager를 통해서만 접근이 가능하다.

엔티티: @Entity 어노테이션을 붙인 클래스

JPA 성능 최적화

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

  1. 모아서 쓰는 버퍼링 기능
  2. 읽을 떄 쓰는 캐싱 기능

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

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

같은 트랜잭션 안에서는 같은 엔티티를 반환

 String memberId = "100";
    
    Member m1 = jpa.find(Member.class, memberId); // SQL
    
    Member m2 = jpa.find(Member.class, memberId); // 캐시 (SQL 1번만 실행, m1을 가져옴)
    
    println(m1 == m2) // true

결과적으로, SQL을 한번만 실행한다.
DB Isolation Level이 Read Commit이어도 애플리케이션에서 Repeatable Read 보장

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

INSERT

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

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

em.persist(memberA);

em.persist(memberB);

em.persist(memberC);

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

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

/** 2. JDBC BATCH SQL 기능을 사용해서 한번에 SQL 전송 */

transaction.commit(); // [트랜잭션] 커밋
  1. [트랜잭션]을 commit 할 때까지 INSERT SQL을 메모리에 쌓는다.
    이렇게 하지 않으면 DB에 INSERT Query를 날리기 위한 네트워크를 3번 타게 된다.

  2. JDBC Batch SQL 기능을 사용해서 한 번에 SQL을 전송한다.
    JDBC Batch를 사용하면 코드가 굉장히 지저분해진다.
    지연 로딩 전략(Lazy Loading) 옵션을 사용한다.

UPDATE

/** 1. UPDATE, DELETE로 인한 로우(ROW)락 시간 최소화 */

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

changeMember(memberA);

deleteMember(memberB);

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

// 커밋하는 순간 데이터베이스에 UPDATE, DELETE SQL을 보낸다.

/** 2. 트랜잭션 커밋 시 UPDATE, DELETE SQL 실행하고, 바로 커밋 */

transaction.commit(); // [트랜잭션] 커밋
  1. UPDATE, DELETE로 인한 로우(ROW)락 시간 최소화
  2. 트랜잭션 커밋 시 UPDATE, DELETE SQL 실행하고, 바로 커밋

지연 로딩(Lazy Loading)

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

memberDAO.find(memberId)에서는 Member 객체에 대한 SELECT 쿼리만 날린다.
Team team = member.getTeam()로 Team 객체를 가져온 후에 team.getName() 처럼 실제로 team 객체를 건드릴 때, 즉, 값이 실제로 필요한 시점에 JPA가 Team에 대한 SELECT 쿼리를 날린다.

Member와 Team 객체 각각 따로 조회하기 떄문에 네트워크를 2번 타게 된다.
Member를 사용하는 경우에 대부분 Team도 같이 필요하다면 즉시 로딩을 사용한다.

즉시로딩

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

Join을 통해 항상 연관된 모든 객체를 같이 가져온다.

애플리케이션을 개발할 때는 모두 지연 로딩으로 설정한 후에, 성능 최적화가 필요할 때에 옵션을 변경하는 것을 추천한다.

profile
저도 모르는데요?, 내가 몰라서 적는 글
post-custom-banner

0개의 댓글