2. JPA 시작

728x90

메이븐이나 IDE (eclipse, IntelliJ ..etc)를 설치하는 방법도 소개가 되어있지만 이는 생략하는 것으로 한다. 또한 메이븐이아닌 그래들을 사용해도 무방하다. (나는 오히려 그래들을 애용하기에 그래들을 사용할 것이다)

H2 데이터베이스 설치 

예제는 MySQL이나 오라클 데이터베이스를 사용해도 되지만 설치하는 부담이 크다. 따라서 설치가 필요 없고 용량도 1.7M로 가벼운 H2 데이터베이스를 사용하겠다. 참고로 데이터베이스는 자바가 설치되어 있어야 동작한다. (실제로 테스트 용도로 로컬 환경 테스트까지 H2 데이터베이스를 사용하기도 한다, 로컬 상황에서는 상황에따라서 MySQL이나 Oracle 같은 상용 데이터베이스를 사용할 수도있고 H2 데이터베이스를 사용할 수도 있다,  주로 테스트 용도라는 것을 기억하자) 

H2 데이터베이스 설치 방법

http://www.h2database.com에 들어가서 All Platforms 또는 Platform-Independent Zip을 내려받아서 압축을 풀자. 압축을 푼 곳에서 bin/h2.sh를 실행하면 H2 데이터베이스를 서버 모드로 실행한다. (H2 데이터베이스는 JVM 메모리 안에서 실행되는 임베디드 모드와 실제 데이터베이스처럼 별도의 서버를 띄워서 동작하는 서버 모드가 있다) 

H2 데이터베이스를 서버 모드로 실행한 후에 웹 브라우저에서 http://localhost:8082를 입력하면 H2 데이터베이스에 접속할 수 있는 아래의 그림과 같은 화면이 나온다. 

위와같이 나오는대로 바로 connect 버튼을 누르면 이상없이 H2 데이터베이스로 접근할 수 있다. 그리고 아래와 같은 화면이 나온다.

 

예제 테이블 생성 

아래의 SQL을 입력하고 실행버튼을 선택하면 생성된 MEMBER 테이블을 볼 수 있다.

CREATE TABLE MEMBER (
    ID VARCHAR(255) NOT NULL.
    VAME VARCHAR(255),
    AGE INTEGER NOT NULL,
    PRIMARY KEY (ID)
)

 

메이븐 혹은 그래들에는 JPA의 구현체로 하이버네이트를 사용하기 위한 핵심 라이브러리를 추가한다. (메이븐 기준)

  • hibernate-core : 하이버네이트 라이브러리

  • hibernate-entitymanager : 하이버네이트가 JPA 구현체로 동작하도록 JPA 표준을 구현한 라이브러리

  • hibernate-jpa-2.1-api : JPA 2.1 표준 API를 모아둔 라이브러리

Maven의 pom.xml 부분의 의존성을 추가한다.

<dependencies>
    <!-- JPA, 하이버네이트-->
    <dependency>
        <groupId>org.hibernate</gorupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <version>4.3.10.Final</version>
    </dependency>
    <!-- H2 데이터베이스 -->
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>1.4.187</version>
    </dependency>
</dependencies>

JPA에 하이버네이트 구현체를 사용하려면 많은 라이브러리가 필요하지만 핵심 라이브러리는 다음 2가지다. 

  • JPA, 하이버네이트(hibernate-entitymanager): JPA 표준과 하이버네이트를 포함되는 라이브러리, hibernate-entitymanager를 라이브러리로 지정하면 다음 중요 라이브러리도 함께 내려받는다.

    - hibernate-core.jar

    - hibernate-jpa-2.1-api.jar

  • H2 데이터베이스: H2 데이터베이스에 접속해야 하므로 h2 라이브러리도 지정했다.

위에서 생성했던 MEMBER 테이블과 매핑할 회원 클래스를 만들자

package jpabook.start;

public class Member {
    private String id;
    private String username;
    private Integer age;
    
    //Getterm Setter
    public String getId() {return id;}
    public void setId(String id) {this.id = id;}
    
    public String getUsername() {return username;}
    public void setUsername(String username){this.username = username;}
    
    public Integer getAge() {return age;}
    public void setAge(Integer age) {this.age = age}
    
}

아래에 표는 MEMBER 테이블과 Member 클래스의 매핑 정보이다.

매핑 정보

회원 객체

회원 테이블

클래스와 테이블

Member

MEMBER

기본 키

id

ID

필드와 컬럼

username

NAME

필드와 컬럼

age

AGE

 

아래 예제처럼 회원 클래스에 JPA가 제공하는 매핑 어노테이션을 추가하자.

package jpabook.start;

import javax.persistence.*;

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

    @Id
    @Column(name = "ID")
    private String id;
    
    @Column(name = "NAME")
    private String username;
    
    //매핑 정보가 없는 필드 
    private Integer Age;
    ...
}

 

회원 클래스에 매핑 정보를 표시하는 어노테이션을 추가했다. 여기서 쓰인 매핑 정보들을 하나씩 살펴보자.

  • @Entity

    이 클래스를 테이블과 매핑한다고 JPA에게 알려준다. 이렇게 @Entityrk 사용된 클래스를 엔티티 클래스라 한다.

  • @Table

    엔티티 클래스에 매핑할 테이블 정보를 알려준다. 여기서는 name 속성을 사용해서 Member 엔티티를 MEMBER 테이블에 매핑했다. 이 어노테이션을 생략하면 클래스 이름을 테이블 이름으로 매핑한다(더 정확히는 엔티티 이름을 사용한다)

  • @Id

    엔티티 클래스의 필드를 테이블의 기본 키(primary key)에 매핑한다. 여기서는 엔티티의 id 필드를 테이블의 ID 기본 키 컬럼에 매핑했다. 이렇게 @Id가 사용된 필드를 식별자 필드라 한다.

  • @Column 

    필드를 컬럼에 매핑한다. 여기서는 name 속성을 사용해서 Member 엔티티의 username 필드를 MEMBER 테이블의 NAME 컬럼에 매핑했다. 

  • 매핑 정보가 없는 필드 

    매핑 어노테이션을 생략하면 필드명을 사용해서 컬럼명으로 매핑한다. 여기서는 필드명이 age이므로 age 컬럼으로 매핑했다. 참고로 데이터베이스가 대소문자를 구분하지 않는다고 가정한다. 만약 대소문자를 구분하는 데이터베이스를 사용한다면 @Column(name = "AGE") 처럼 명시한다. 

 

persistence.xml 설정

JPA는 persistence.xml을 사용해서 필요한 설정 정보를 관리한다. 이 설정 파일이 METE-INF/persistence.xml 클래스 패스 경로에 있으면 별도의 설정 없이 JPA가 인식할 수 있다.

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="httl://xmlns.jcp.org/xml/ns/persistence" version="2.1">
  <persistence-unit name="jpabook" >
    <properties>
        
        <!-- 필수 속성-->
        <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
        <property name="javax.persistence.jdbc.user" value="sa"/>
        <property name="javax.persistence.jdbc.password" value=""/>
        <property name="javax.persistence.jdbc.url" value="org.hibernate.dialect.H2Dialect"/>
        
        <!-- 옵션-->
        <property name="hibernate.show_sql" value="true" />
        <property name="hibernate.format_sql" value="true" />
        <property name="hibernate.use_sql_comments" value="true" />
        <property name="hibernate.id.new_generator_mappings" value="true" />
        
    </properties>
  </persistence-unit>
</persistence>

 

<persistence xmlns="httl://xmlns.jcp.org/xml/ns/persistence" version="2.1">

먼저 설정 파일은 persistence로 시작한다. 이곳에 XML 네임스페이스와 사용할 버전을 지정한다. JPA 2.1을 사용하려면 이 xmlns와 version을 명시하면 된다.

<persistence-unit name="jpabook" >

JPA 설정은 영속성 유닛(persistence-unit)이라는 것부터 시작하는데 일반적으로 연결할 데이터베이스당 하나의 영속성 유닛을 등록한다. 그리고 영속성 유닛에는 고유한 이름을 부여해야 하는데 여기서는 jpabook이라는 이름을 사용했다. 

다음으로 설정한 각각의 속성 값을 분석해보자.

<properties>
        
        <!-- 필수 속성-->
        <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
      ...

사용한 속성은 다음과 같다. 

  • JPA 표준 속성

    - javax.persistence.jdbc.driver : JDBC 드라이버 

    - javax.persistence.jdbc.user : 데이터베이스 접속 아이디

    - javax.persistence.jdbc.password : 데이터베이스 접속 비밀번호

    - javax.persistence.jdbc.url : 데이터베이스 접속 URL

  • 하이버네이트 속성 

    - hibernate.dialect : 데이터베이스 방언(Dialect) 설정 

이름이 javax.persistence로 시작하는 속성은 JPA 표준 속성으로 특정 구현체에 종속되지 않는다. 반면에 hibernate로 시작하는 속성은 하이버네이트 전용 속성이므로 하이버네이트에서만 사용할 수 있다. 사용한 속성을 보면 데이터베이스에 연결하기 위한 설정이 대부분이다.여기서 가장 중요한 부분은 hibernate.dialect다.

 

데이터베이스 방언 

JPA는 특정 데이터베이스에 종속적이지 않은 기술이다. 따라서 다른 데이터베이스로 손쉽게 교체할 수 있다. 그런데 각 데이터베이스가 제공하는 SQL 문법과 함수가 조금씩 다르다는 문제점이 있다. 

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

이처럼 SQL 표준을 지키지 않거나 특정 데이터베이스만의 고유한 기능을 JPA에서는 방언(DIalect)이라 한다. 애플리케이션 개발자가 특정 데이터베이스에 종속되는 기능을 많이 사용하면 나중에 데이터 베이스를 교체하기가 어렵다. 하이버네이트를 포함한 대부분의 JPA 구현체들은 이런 문제를 해결하려고 다양한 데이터베이스 방언 클래스를 제공한다.

개발자는 JPA가 제공하는 표준 문법에 맞추어 JPA를 사용하면 되고, 특정 데이터베이스에 의존적인 SQL은 데이터베이스 방언이 처리해준다. 따라서 데이터베이스가 변경되어도 애플리케이션 코드를 변경할 필요 없이 데이터베이스 방어만 교체하면 된다. (데이터베이스 방언을 설정하는 방법은 JPA에 표준화되어 있지 않다)

지금까지 persistence.xml 설정을 진행하였다. 이제 이 정보를 바탕으로 실제 JPA를 사용해본다.
아래의 예제는 애플리케이션을 시작하는 코드다.

package jpabook.start;

import javax.persistence.*
import java.util.List;

public class JpaMain {
    
    public static void main(String[] args) {
        
        //엔티티매니저 팩토리
        EntityManagerFactory = emf = Persistence.createEntityManagerFactory("jpabook");
        
        //엔티티매니저 
        EntityManager em = emf.createEntityManager();
        
        //트랜잭션
        EntityTransaction tx = em.getTransaction();
        
        try {
            tx.begin(); //트랜잭션 시작
            logic(em); //비즈니스 로직
            tx.commit(); //트랜잭션 커밋
        } catch (Exception e) {
            tx.rollback(); //트랜잭션 롤백
        } finally {
            em.close(); //엔티티매니저 종료
        }
        emf.close(); //엔티티 매니저 팩토리 종료
    }
    
    //비즈니스 로직
    private static void logic(EntityManger em) {...}
}

 

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

  • 엔티티 매니저 설정

  • 트랜잭션 관리

  • 비즈니스 로직

엔티티 매니저 설정부터 살펴보자

엔티티 매니저 설정 

  • 엔티티 매니저 팩토리 생성

    JPA를 시작하려면 우선 persistence.xmldml의 설정 정보를 사용해서 엔티티 매니저 팩토리를 생성해야 한다. 이때 Persistence 클래스를 사용하는데 이 클래스는 엔티티 매니저 팩토리를 생성해서 JPA를 사용할 수 있게 준비한다. 이렇게 하면 META-INF/persistence.xml에서 이름이 jpabook인 영속성 유닛 persistence-unit을 찾아서 엔티티 매니저 팩토리를 생성한다. 이때 persistence.xml의 설정 정보를 읽어서 JPA를 동작시키기 위한 기반 객체를 만들고 JPA 구현체에 따라서는 데이터베이스 커넥션 풀도 생성하므로 엔티티 매니저 팩토리를 생성하는 비용은 아주 크다. 따라서 엔티티 매니저 팩토리는 애플리케이션 전체에서 딱 한번만 생성하고 공유해서 사용해야 한다.

  • 엔티티 매니저 생성

    엔티티 매니저 팩토리에서 엔티티 매니저를 생성한다. JPA의 기능 대부분을 이 엔티티 매니저가 제공한다. 대표적으로 엔티티 매니저를 사용해서 엔티티를 데이터베이스에 등록/수정/삭제/조회할 수 있다. 엔티티 매니저는 내부에 데이터소스(데이터베이스 커넥션)를 유지하면서 데이터베이스와 통신한다. 따라서 애플리케이션 개발자는 엔티티 매니저를 가상의 데이터베이스로 생각할 수 있다. 참고로 엔티티 매니저는 데이터베이스 커넥션과 밀접한 관계가 있으므로 스레드간에 공유하거나 재사용해서는 안된다.

  • 종료

    마지막으로 사용이 끝난 엔티티 매니저는 다음처럼 반드시 종료해야 한다.

    em.close(); // 엔티티 매니저 종료

    애플리케이션을 종료할 때 엔티티 매니저 팩토리도 다음처럼 종료해야 한다.

    emf.close(); //엔티티 매니저 팩토리 종료 

 

트랜잭션 관리

 

public static void logic(EntityManager em) {
    
    String id = "id1";
    Member member = new Member();
    member.setId(id);
    member.setUsername("지한");
    member.setAge(2);
    
    //등록
    em.persist(member);
    
    //수정
    em.setAge(20);
    
    //한 건 조회
    Member findMember = em.find(Member.class, id);
    System.out.println("findMember=" + findMember.getUsername() + ", age=" 
            + findMember.getAge());
    
    //목록 조회
    List<Memger> members = em.createQuery("select m from Member m", Member.class).getReusltList();
    System.out.println("mbmers.size=" + members.size());
    
    //삭제
    em.remove(member);
}

출력 결과는 다음과 같다. 

findMember=지한, age=20
members.size=1

비즈니스 로직을 보면 등록, 수정, 삭제, 조회 작업이 엔티티 매니저를 통해서 수행되는 것을 알 수 있다. 엔티티 매니저는 객체를 저장하는 가상의 데이터베이스처럼 보인다.

 

JPQL

하나 이상의 회원 목록을 조회하는 다음 코드를 자세히 살펴보자.

//목록 조회
TypedQuery<Member> query = em.createQuery("select m from Member m", Member.class);
List<Member> members = query.getResultList();

JPA를 사용하면 애플리케이션 개발자는 엔티티 객체를 중심으로 개발하고 데이터베이스에 대한 처리는 JPA에 맡겨야 한다. 바로 앞에서 살펴본 등록, 수정, 삭제, 한건 조회 예를 보면 SQL을 전혀 사용하지 않았다. 문제는 검색 쿼리다. JPA는 엔티티 객체를 중심으로 개발하므로 검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 검색해야 한다. 

그런데 테이블이 아닌 엔티티 객체를 대상으로 검색하려면 데이터베이스의 모든 데이터를 애플리케이션으로 불러와서 엔티티 객체로 변경한 다음 검색해야 하는데, 이는 사실상 불가능하다. 애플리케이션이 필요한 데이터만 데이터베이스에서 불러오려면 결국 검색 조건이 포함된 SQL을 사용해야 한다. JPA는 JPQL(Java Persistence Query Language)이라는 객체지향 쿼리 언어를 제공한다. JPQL은 SQL과 문법이 거의 유사해서 SELECT, FROM, WHERE, GROUP, BY, HAVING, JOIN 등을 사용할 수 있다. 둘의 차이점은 아래와 같다.

  • JPQL은 엔티티 객체를 대상으로 쿼리한다.
  • SQL은 데이터베이스 테이블을 대상으로 쿼리한다.

방금 본 목록 조회에서 select m from Member m 이 바로 JPQL이다. 여기서 form Member는 회원 엔티티 객체를 말하는 것이지 MEMBER 테이블이 아니다. JPQL을 사용하려면 먼저 em.createQuery(JPQL, 반환타입) 메서드를 실행해서 쿼리 객체를 생성한 후 쿼리 객체의 getResultList() 메서드를 호출한다. JPA는 JPQL을 분석해 적절한 SQL을 만들어 데이터베이스에서 데이터를 조회한다. 

(JPQL은 대소문자를 명확하게 구분하지만 SQL은 관례상 대소문자를 구분하지 않고 사용하는 경우가 많다. ) 

728x90

'프로그래밍 공부 > JPA' 카테고리의 다른 글

Querydsl - 기본문법  (0) 2022.07.21
3. 영속성 관리  (0) 2020.03.14
1. JPA 소개  (0) 2020.03.10