JPA

[JPA] 배워서 엔터프라이즈 개발하자 2일차 - JPA 시작

suhwanc 2022. 3. 12. 03:33

본 글은 김영한님의 [인프런 - 자바 ORM 표준 JPA 프로그래밍 - 기본편] 강의와 자바 ORM 표준 JPA 프로그래밍 책을 기반으로 정리해 작성하였습니다.

자바 ORM 표준 JPA 프로그래밍 책

 

이번 장에서는 원래 스프링 프로젝트 생성, 메이븐, h2 Database 설정 등을 해야 하는데, 이런 부분은 사용자나 하고 싶은 프로젝트마다 다를 수 있기 때문에 포스팅에서는 생략하고 간단한 어노테이션이나 엔티티 매니저 같은 기본적인 부분을 알아보고 넘어가도록 하겠다.

 

 

1. 객체 매핑 시작


JPA를 사용하려면 가장 먼저 테이블들을 매핑해야 한다. 1일차에서 살펴본 멤버 테이블을 매핑해보자.

 

package hellojpa;
import javax.persistence.*;

@Entity
@Table(name = "MEMBER")
public class Member {

    @Id
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "USERNAME")
    private String username;
    
    @Column(name = "TEAM")
    private Team team;

	private int age;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Team getTeam() {
        return team;
    }

    public void setTeam(Team team) {
        this.team = team;
    }

}

 

위 코드에서 사용된 어노테이션("@" 붙은 것들)들을 알아보자.

 

  • @Entity : 이 클래스를 테이블과 매핑한다고 JPA에게 알려준다. 이렇게 @Entity가 사용된 클래스를 엔티티 클래스라고 한다.
  • @Table : 엔티티 클래스에 매핑할 테이블 정보를 알려준다. 여기서는 name 속성을 사용해 Member 엔티티를 MEMBER 테이블에 매핑했다. 만약 이 어노테이션을 생략하면 클래스 이름을 테이블 이름으로 매핑한다.
  • @Id : 엔티티 클래스의 필드를 테이블의 기본 키(Primary Key)에 매핑한다. 이렇게 @Id가 사용된 필드를 식별자 필드라고 한다.
  • @Column : 필드를 컬럼에 매핑한다. 여기서는 name 속성을 사용해서 Member 엔티티의 username 필드를 MEMBER 테이블의 USERNAME 컬럼에 매핑했다.
  • 매핑 정보가 없는 필드 : 이렇게 생략된 필드들은 필드명을 사용해서 컬럼명으로 매핑한다. 따라서 위의 age 필드는 age 그대로 컬럼에 매핑될 것이다.

 

 

2. 데이터베이스 방언


JPA는 특정 데이터베이스에 종속적이지 않은 기술이다. 따라서 다른 데이터베이스로 손쉽게 교체할 수 있다.

하지만 데이터베이스마다 제공하는 SQL 문법과 함수가 다르다는 문제점이 있는데 예를 들면 이런게 있다.

 

  • 데이터 타입 : 가변 문자 타입으로 MySQL은 VARCHAR, 오라클은 VARCHAR2를 사용한다.
  • 다른 함수명 : 문자열을 자르는 함수로 SQL 표준은 SUBSTRING(), 오라클은 SUBSTR()
  • 페이징 처리 : MySQL은 LIMIT, 오라클은 ROWNUM

 

이처럼 언어마다 다른 고유한 기능을 JPA에서는 방언(Dialect)이라 한다.

JPA 구현체들은 이런 문제를 해결하기 위해 다양한 데이터베이스 방언 클래스를 제공한다.

 

Hibernate가 지원하는 데이터베이스 방언들은 여기서 확인해볼 수 있다. 

https://docs.jboss.org/hibernate/orm/3.5/javadocs/org/hibernate/dialect/package-summary.html

 

 

 

3. 애플리케이션 개발


이제 실제 JPA 애플리케이션 코드를 살펴보자.

 

package hellojpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

public class JpaMain {

    public static void main(String[] args) {
        // 엔티티 매니저 팩토리 생성
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        // 엔티티 매니저 생성
        EntityManager em = emf.createEntityManager();
        //트랜잭션 획득
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {
            tx.begin(); //트랜잭션 시작
            logic(em); // 비즈니스 로직 실행
            tx.commit(); //트랜잭션 커밋
        } catch (Exception e) {
            tx.rollback(); //트랜잭션 롤백
        } finally {
            em.close(); // 엔티티 매니저 종료
        }
        emf.close(); // 엔티티 매니저 팩토리 종료
        
        
    }
    //비즈니스 로직
    private static void logic(EntityManager em) {
        //비즈니스 로직
    }
}

 

코드는 크게 3부분으로 나뉘어 있다.

  • 엔티티 매니저 설정
  • 트랜잭션 관리
  • 비즈니스 로직

 

1) 엔티티 매니저 설정

JPA를 시작하려면 일단 엔티티 매니저 팩토리를 생성해야 한다. 이때 Persistence 클래스를 사용하게 된다.

EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");

 

이때 persistence.xml의 설정 정보를 읽어서 JPA를 동작시키기 위한 기반 객체를 만들고 JPA 구현체에 따라서 데이터베이스 커넥션 풀도 생성하므로 엔티티 매니저 팩토리를 생성하는 비용은 아주 크다.

따라서 엔티티 매니저 팩토리는 애플리케이션 전체에서 딱 한 번만 생성하고 공유해서 사용해야 한다.

 

2) 엔티티 매니저 생성

EntityManager em = emf.createEntityManager();

엔티티 매니저 팩토리에서 엔티티 매니저를 생성한다. JPA의 기능 대부분은 이 엔티티 매니저가 제공한다.

엔티티 매니저를 사용해서 엔티티를 데이터베이스에 CRUD 할 수 있고, 내부에 데이터베이스 커넥션을 유지하면서 데이터베이스와 통신한다. 즉, 가상의 데이터베이스 역할을 하는 셈이다.

엔티티 매니저는 데이터베이스 커넥션과 밀접한 관계가 있으므로 스레드 간에 공유하거나 재사용하면 안된다.

 

3) 종료

em.close();
emf.close();

사용이 끝난 엔티티 매니저와 팩토리는 반드시 종료해야 한다.

 

 

4) 트랜잭션 관리

EntityTransaction tx = em.getTransaction(); //트랜잭션 API
try {
	tx.begin(); //시작
    logic(em); //비즈니스 로직 실행
    tx.commit(); //커밋
} catch (Exception e) {
	tx.rollback(); //예외 발생 시 트랜잭션 롤백
}

 

JPA를 사용하면 항상 트랜잭션 안에서 데이터를 변경해야 한다. 그렇지 않으면 예외가 발생한다.

트랜잭션을 시작하려면 엔티티 매니저에서 트랜잭션 API를 받아와야 한다.

 

 

5) 비즈니스 로직

 

비즈니스 로직은 앞에서 설명한 JPA에서 지원하는 함수(persist, remove 등..) 또는 직접 만든 SQL을 사용하는 실제 구현 기능들이다.

모든 작업들은 엔티티 매니저(em)을 통해 수행된다.

 

 

4. JPQL


앞서 말했듯, JPA는 엔티티 객체를 중심으로 개발하므로 데이터베이스에서 검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 검색해야 한다.

그런데 검색 쿼리에 조건을 달아서 검색하려면 데이터베이스의 모든 데이터를 애플리케이션으로 불러와 객체를 변경한 다음 검색해야 하는데, 이는 사실상 불가능하다.

 

따라서 결국 검색 조건이 포함된 SQL을 사용해야만 하는 상황이 발생하고, JPA는 JPQL이라는 쿼리 언어로 이런 문제를 해결한다.

JPQL이란 SQL을 추상화한 객체지향 쿼리 언어이다. SQL과 문법이 거의 유사하며 SELECT, FROM, WHERE 뿐만 아니라 GROUP BY, HAVING 등을 사용할 수 있다.

 

하지만 JPQL은 엔티티 객체를 대상으로 쿼리하는 것이지, 데이터베이스 테이블을 전혀 알지 못한다. 

 

실제 사용 예시

public List<OrderSimpleQueryDto> findOrderDtos() {
    return em.createQuery(
            "select new jpabook.jpashop1.repository.order.simplequery.OrderSimpleQueryDto(o.id, m.name, o.orderDate, o.status, d.address) " +
                    " from Order o" +
                    " join o.member m" +
                    " join o.delivery d", OrderSimpleQueryDto.class).getResultList();
}

 

여기서 Order, Member는 모양을 보면 알겠지만 객체이지 테이블이 아니라는 점을 꼭 알아두자!