본문 바로가기

Java

JPA (Java Persistence API)

ORM

  • ORM (Object-Relational Mapping)
  • 애플리케이션 Class와 RDB(Relational DataBase)의 테이블을 매핑 (연결)
  • 어플리케이션의 객체를 RDB 테이블에 자동으로 영속화
  • 객체는 객체대로 설계하고, 관계형 데이터베이스는 관계형 데이터베이스대로 설계한다.
  • ORM 프레임워크가 중간에서 매핑해 준다.
  • 대중적인 언어에는 대부분 ORM 기술이 존재한다.

 

ORM 장점

  • SQL문이 아닌 Method를 통해 DB를 조작할 수 있어, 개발자는 객체 모델을 이용하여 비즈니스 로직을 구성하는데만 집중할 수 있음
    • 내부적으로는 쿼리를 생성하여 DB를 조작함
    • 하지만 개발자가 이를 신경 쓰지 않아도 됨
  • Query와 같이 필요한 선언문, 할당 등의 부수적인 코드가 줄어들어 각종 객체에 대한 코드를 별도로 작성하여 코드의 가독성을 높임
  • 객체지향적인 코드 작성이 가능함. 오직 객체지향적 접근만 고려하면 되기때문에 생산성 증가
  • 매핑하는 정보가 Class로 명시 되었기 때문에 ERD를 보는 의존도를 낮출 수 있고 유지보수 및 리팩토링에 유리
  • 기존 방식에서 MySQL 데이터베이스를 사용하다가 ORACLE로 변환한다고 가정해보면, 새로 쿼리를 짜야하는 경우가 생김. 이런 경우에 ORM을 사용한다면 쿼리를 수정할 필요가 없음

 

ORM 단점

  • 프로젝트의 규모가 크고 복잡하여 설계가 잘못된 경우, 속도 저하 및 일관성을 무너뜨리는 문제점이 생길 수 있음
  • 복잡하고 무거운 Query는 속도를 위해 별도의 튜닝이 필요하기 때문에 결국 SQL문을 써야할 수도 있음

 

ORM과 SQL Mapper와의 차이점

1. SQL Mapper

  • myBatis, jdbcTemplate
  • SQL을 명시하여 직접 DB 조작
  • 필드를 매핑시키는 것이 목적
  • 자바 클래스와 sql을 매핑

2. ORM

  • JPA, Hibernate
  • 자바 클래스와 DB 테이블을 매핑
  • 객체간의 관계를 바탕으로 SQL 자동 생성
  • RDB의 관계를 Object 에 반영하는 것이 목적

 

JPA

  • JPA(Java Persistence API)
  • JPA는 자바 진영에서 ORM(Object-Relational Mapping) 기술 표준으로 사용되는 인터페이스의 모음
  • JPA를 구현한 대표적인 오픈소스로는 Hibernate가 있음
  • 자바 어플리케이션에서 관계형 데이터베이스를 사용하는 방식을 정의한 인터페이스
  • JPA는 API의 규격 인터페이스일 뿐 (라이브러리나 프레임워크가 아님)
  • JPA 2.1 표준 명세를 구현한 구현체 : Hibernate, EclipseLink, DataNucleus, OpenJPA, TopLink Essentials 등이 있다. (ORM 프레임워크)

 

  • JPA 는 애플리케이션과 JDBC 사이에서 동작하여 개발자가 JPA 를 사용하면, JPA 내부에서 JDBC API 를 사용하여 SQL 을 호출, DB 와 통신한다.

 

 

왜 JPA를 사용해야 할까?

1. 생산성

  • JPA를 사용하면 자바 컬렉션에 저장하듯이 JPA에게 저장할 객체를 전달하면 된다.
  • 지루하고 반복적인 코드를 개발자가 직접 작성하지 않아도 되며, DDL문도 자동으로 생성해주기 때문에 데이터베이스 설계 중심을 객체 설계 중심으로 변경할 수 있다.

2. 유지보수

  • 필드를 하나만 추가해도 관련된 SQL과 JDBC 코드를 전부 수행해야 했지만 JPA는 이를 대신 처리해주기 때문에 개발자가 유지보수해야하는 코드가 줄어든다.
  • SQL을 직접 작성하는 것이 아닌, 객체를 사용하여 동작 -> 재사용성 up
  • 기존 SQL 방식 : 테이블 필드 변경시 모든 SQL을 수정해야 한다.
  • JPA 방식 : Entity 객체에 필드만 추가하면 된다.

3. 패러다임의 불일치 해결

  • JPA는 연관된 객체를 사용하는 시점에 SQL을 전달할 수 있고, 같은 트랜잭션 내에서 조회할 때 동일성도 보장하기 때문에 다양한 패러다임의 불일치를 해결한다.

4. 성능

  • 애플리케이션과 데이터베이스 사이에서 성능 최적화 기회를 제공한다.
  • 같은 트랜잭션안에서는 같은 엔티티를 반환하기 때문에 데이터 베이스와의 통신 횟수를 줄일 수 있다.
  • 트랜잭션을 commit하기 전까지 메모리에 쌓고 한번에 SQL을 전송한다.

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

  • RDB는 같은 기능이라도 벤더마다 사용법이 다르기 때문에 처음 선택한 데이터베이스에 종속되고 변경이 어렵다. JPA는 애플리케이션과 데이터베이스 사이에서 추상화된 데이터 접근을 제공하기 때문에 종속이 되지 않도록한다.
  • 만약 DB가 변경되더라도 JPA에게 알려주면 간단하게 변경이 가능하다.

 

 

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를 통해서 가져오는 경우
    • 객체 그래프를 완전히 자유롭게 탐색할 수 있게 된다.
    • 동일한 트랜잭션에서 조회한 엔티티는 같음을 보장한다.

 

JPA의 성능 최적화 기능

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

  • 같은 트랜잭션 안에서는 같은 엔티티를 반환 - 조회 성능 향상
  • 동일 조건인 경우 쿼리를 2번 실행하지 않고, 캐시에서 읽어들임
  • String memberId = "100"; Member member1 = jpa.find(Member.class, memberId); // DB에서 가져옴 Member member2 = jpa.find(Member.class, memberId); // 1차 캐시에서 가져옴 member1 == member2; // 같다

 

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

(1) INSERT

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

// 2. 커밋하는 순간 데이터베이스에 INSERT 쿼리를 일괄 처리한다.
// JDBC BATCH SQL 기능을 사용해서 한번에 SQL 전송
transaction.commit();	// 트랜잭션 커밋

 

(2) UPDATE

// 1. UPDATE, DELETE로 인한 로우 락(ROW LOCK) 시간 최소화
transaction.begin();	// 트랜잭션 시작
changeMember(memberA);
deleteMember(memberB);

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

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

 

3. 지연 로딩(Lazy Loading)

(1) 지연 로딩

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

 

(2) 즉시 로딩

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

어플리케이션 개발시 기본적으로 지연 로딩을 설정한 후 필요한 경우에 즉시 로딩으로 옵션을 변경하는 것을 추천한다.

 

상속 관계

  • JAVA에서는 부모클래스와 자식클래스의 관계 즉, 상속관계가 존재하는데 데이터베이스에서는 이러한 객체의 상속관계를 지원하지 않는다. 이런 상속관계를 JPA는 아래와 같은 방식으로 해결하였다.

 

  • 위의 구조에서 만약 Album 클래스를 저장한다고 가정해보자.
  • // Album 객체저장 jpa.persist(album);
  • 그러면 JPA는 위의 코드를 아래의 쿼리로 변환해서 실행한다.
  • INSERT INTO ITEM (ID, NAME, PRICE) ..... INSERT INTO ALBUM (ARTIST) .....
  • 위처럼 저장하면 당연히 조회할때도 두 테이블을 엮어서 가져올 것이다. 조회하는 JAVA코드와 변환되는 쿼리를 보도록하자.
  • // JAVA 코드 String albumId = "id100"; Album album = jpa.find(Album.class, albumId); // 변환된 쿼리 SELECT I.*, A.* FROM ITEM I JOIN ALBUM A ON I.ITEM_ID = A.ITEM_ID

 

연관 관계

  • 위와 같이 상속관계에 대한 접근도 제공해주는데 객체지향에는 연관관계라는 것도 있다.
  • 코드로 따지면 Class에서 또 다른 Class Type을 필드 변수로 가지고 있는것이다.
  • 객체관계와 이를 테이블 구조로 나타낸 아래의 그림을 보도록하자.

 

  • 위의 그림은 Member 클래스가 Team 타입의 team 필드 변수를 가지고 있는 형태인데, 코드로 나타내면 아래와 같다.
  • class Member { String id; Team team; String username; } class Team { Long id; String name; }
  • 그렇다면 Team 객체를 참조하는 필드를 가지고 있는 Member 객체는 어떻게 저장할까?
  • Member member = new Member(); member.setId("100"); member.setUsername("dbjh"); Team team = new Team(); team.setName("dev_team"); member.setTeam(team); jpa.persist(member);
  • 위처럼 Member 객체의 team 필드에 Team 객체를 set하고 Member 객체를 DB에 저장하게 되면 JPA는 아래와 같은 코드를 데이터베이스에게 실행하라고 할것이다.
  • INSERT INTO MEMBER (ID, TEAM_ID, USERNAME) .... INSERT INTO TEAM (ID, NAME) ....
  • 이렇게 저장 후 Member 객체만 조회하면, Team 객체 정보도 가져와서 Member 객체의 team필드에 주입해주기 때문에 아래와 같이 사용할 수 있다.
  • // JAVA 코드 Member member = jpa.find(Member.class, memberId); Team team = member.getTeam(); // 변환된 쿼리 SELECT M.*, T.* FROM MEMBER M JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
  • 위와 같은 구조들이 더 복잡해진다고 해도 JPA는 이를 모두 지원해주기 때문에 문제없이 사용할 수 있다.

 

JPA의 저장 및 조회는 아래와 같은 구조로 실행

**저장** : jpa.persist(member)

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

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

**삭제** : jpa.remove(member)

 

[저장]

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는 데이터 수정시, 매핑된 객체(테이블 데이터)를 조회해서 값을 변경 후 커밋하면 DB 서버에 UPDATE 문을 전송하여 UPDATE를 실행