JPA Example

실제 예제릍 통해 JPA를 이용해보자

Development Environment

개발 환경으로는 IntelliJ 와 H2 database를 활용한다. 라이브러리 관리를 위해 Maven을 이용한다.

Project의 디렉토리 구조는 아래와 같다. project_structure

JPA의 구현체인 하이버네이트를 사용하기 아래와 같다

library description
hibernate-core 하이버네이트 라이브러리
hibernate-entitymanager 하이버네이트가 JPA 구현체로 동작하도록 JPA 표준을 구현한 라이브러리
hibernate-jpa-2.1-api JPA 2.1 표준 API를 모아둔 라이브러리

pom.xml

maven을 통해서 라이브러리를 관리하게 되는데, xml 에 적힌 설정을 토대로 maven이 자동으로 라이브러리를 내려받아서 관리한다.

실제로 개발자가 모든 라이브러리를 다운 받아서 관리하는 것은 무척 어렵다.

버전 관리나, 의존성 있는 라이브러리들을 maven을 이용해서 효율적으로 처리할 수 있다

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>jpabook</groupId>
    <artifactId>ch02-jpa-start1</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>

        <!-- 기본 설정 -->
        <java.version>1.6</java.version>
        <!-- 프로젝트 코드 인코딩 설정 -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

        <!-- JPA, 하이버네이트 버전 -->
        <hibernate.version>4.3.10.Final</hibernate.version>
        <!-- 데이터베이스 버전 -->
        <h2db.version>1.4.187</h2db.version>

    </properties>


    <dependencies>
        <!-- JPA, 하이버네이트 -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>${hibernate.version}</version>
        </dependency>
        <!-- H2 데이터베이스 -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>${h2db.version}</version>
        </dependency>
    </dependencies>

</project>

dependecy 태그를 이용해서 필요한 라이브러리들을 명시한다.

jpa, 하이버네이트 라이브러리를 다운 받고 있는데, 하이버네이트(hibernate-entitymanager)을 다운 받게 되면, hibernate-core 와 hibernate-jpa-2.1-api 라이브러리를 자동적으로 다운 받는다.

Application Example

Object Mapping

Member Table

h2 db에 아래의 테이블을 만든다.

CREATE TABLE MEMBER (
    ID LONG AUTO_INCREMENT NOT NULL,   -- 아이디(기본키)
    NAME VARCHAR(255),                 -- 이름
    AGE INTEGER NOT NULL,              -- 나이
    PRIMARY KEY (ID)
)

Member class

자바에는 아래와 같은 Member Class을 만든다.

package jpabook.start;

public class Member {

    private Long id;
    private String username;
    private Integer 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 Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

Member class에 객체 매핑을 위한 annotation을 추가한다.

Annotations

import javax.persistence.*;

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

    @Id
    @Column(name = "ID")
    private String id;

    @Column(name = "NAME")
    private String username;
    ...
}
Annotations Descriptions
@Entity 해당 클래스가 엔티티로 활용됨을 뜻한다.
@Table 엔티티에 대응되는 DB의 table을 명시한다.
@Id primary key 정보를 뜻한다.
@Column 해당 멤버변수를 테이블의 컬럼에 대응시킨다.
  아무런 매핑정보가 없으면 멤버변수 이름이 컬럼의 변수 이름으로 매핑된다.

Persistence.xml 설정

JPA 관련 설정파일로 META-INF/persistece.xml상에 위치한다.

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://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="jdbc:h2:tcp://localhost/~/test"/>
            <property name="hibernate.dialect" 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" />

            <!--<property name="hibernate.hbm2ddl.auto" value="create" />-->
        </properties>
    </persistence-unit>

</persistence>

JPA version

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

해당 태그의 의미는 JPA2.1를 사용한다는 뜻이다.

Persistence Unit

<persistence-unit name="jpabook">

이는 영속성 유닛을 설정한다는 의미인데, DB 하나당 영속성 유닛 하나를 등록한다, 또한 영속성 유닛에는 고유한 이름을 할당한다.

JPA 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="jdbc:h2:tcp://localhost/~/test"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
Properties Descriptions
javax.persistence.jdbc.driver JDBC 드라이버
javax.persistence.jdbc.user DB user 정보
javax.persistence.jdbc.password DB password 정보
javax.persistence.jdbc.url DB 접속 url 정보
hibernate.dialect 데이터베이스 방언??

Hibernate Properties

 <!-- 옵션 -->
<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" />

javax.persistence로 시작하는 속성들은 JPA에서 사용되는 표준 속성들로, 특정 구현체에 종속되지 않는다. 단, hibernate로 시작되는 속성은 hibernate에 국한된 속성들이다.

Hibernate 관련 속성 정보이다. |Properties|Descriptions| |–|–| |hibernate.show_sql|실행한 SQL을 출력| |hibernate.format_sql|SQL을 보기 좋게 정렬함| |hibernate.use_sql_comments| 쿼리 출력 시 주석도 함께 출력| |hibernate.id.new_generator_mappings| JPA 표준에 맞는 새로운 키 생성 전략을 사용함 –> 나중에 자세한 내용 추가|

데이터베이스 방언

JPA는 특정 DB에 종속적이지 않는 기술로, 여러 DB에 대해 활용이 가능하도록 지원해준다. 하지만, DB 마다 SQL 문법이 조금씩 다른데, 어떻게 이를 지원해주는가?

차이점

데이터 타입: MySQL에서는 VARCHAR을 쓰지만, Oracle에서는 VARCHAR2를

함수명: MySQL의 SUBSTRING() 과 Oracle SUBSTR()

페이징 처리: MySQL의 LIMIT 과 Oracle의 ROWNUM

이렇듯, DB마다 조금씩 차이가 있다. 이 처럼 SQL 표준이 아닌, DB마다 조금씩 차이나는 부분을 데이터베이스 방언이라고 한다.그래서 JPA는 이러한 데이터베이스 방언을 처리하기 위해 데이터베이스 방언 클래스를 활용한다. 개발자는 JPA 문법에 맞게 JPA를 사용하기만 하면, JPA 내부적으로 방언 클래스를 이용해서 자동으로 변환된다.

대표적으로 아래와 같은 방언들이 있다.

db dialects
H2 org.hibernate.dialect.H2Dialect
MySQL org.hibernate.dialect.MySQL5InnoDBDialect
Oracle10g org.hibernate.dialect.Oracle10gDialect

위의 예제에서는 h2 DB의 dialect을 활용한다.

Application

Main class

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) {
        e.printStackTrace();
        tx.rollback(); //트랜잭션 롤백
    } finally {
        em.close(); //엔티티 매니저 종료
    }

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

메인 클래스의 구성을 보면 아래와 같이 구성되어 있다.

  1. Entity Manager 설정
  2. Transaction Manager 설정
  3. Busniness Logic

Entity Manager

entity_manager

Entity Manager Factory

JPA를 시작하기 위해, persistence.xml의 영속성 유닛(persistence-unit)의 정보를 받아와서 Entity Manager Factory 객체를 생성한다. 이때, persistence-unit의 name으로 등록한 ‘jpabook’을 이용해서 가져온다.

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

Entity Manager Factory는 JPA를 동작시키기 위한 기반 클래스로, 경우에 따라서 Connection Pool을 만들기도 하는 매우 무거운 클래스이므로 애플리케이션 진행에 있어 한번만 생성해서 전반에 걸쳐서 공유하여 사용한다.

Entity Manager

EntityManager em = emf.createEntityManager();

entity manger factory를 이용해서 entity manager을 생성한다. 이 entity manager 내부적으로 DB connection에 대한 정보를 유지하고 있어 entity에 대한 CRUD를 진행할 수 있다. 해당 entity manger 각각은 내부적으로 DB connection을 가지고 있으며 Thread 간 공유되어선 안된다.

Close

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

entity 사용이 완료되면, entity manager를 종료하고, app 사용이 끝나면, entity manager factory도 종료시킨다.

Transactions

EntityTransaction tx = em.getTransaction(); //트랜잭션 기능 획득
try {
    tx.begin(); //트랜잭션 시작
    logic(em);  //비즈니스 로직
    tx.commit();//트랜잭션 커밋

} catch (Exception e) {
    e.printStackTrace();
    tx.rollback(); //트랜잭션 롤백
}

데이터 변경는 항상 트랜잭션 내에서 이루어져야 한다. 트랜잭션 또한, entity manager 이용해서 받아온다.

성공적으로 작업을 완료하면 commit을, 실패하면 rollback 처리를 한다.

Business Logic

Entity Class를 이용해서 실제로 DB에 CRUD을 진행하는 부분이다. CRUD 기능은 EntityManager 객체를 이용해서 진행한다.

Create

public static void logic(EntityManager em){
    //Member 객체 생성
    String id = "id1";
    Member member = new Member();
    member.setId(id);
    member.setUsername("지한");
    member.setAge(2);

    //등록
    em.persist(member); 

em.persist을 통해 내부적으로 아래의 SQL이 실행된다.

INSERT INTO MEMBER(ID,NAME,AGE) VALUES('id1','지한',2)

Read

//조회
Member findMember = em.find(Member.class, id);
System.out.println("findMember=" + findMember.getUsername() + ", age=" + findMember.getAge());

find 메소드를 이용해서, @Id 기반으로 객체를 조회할 수 있다.

SELECT * FROM MEMBER WHERE ID='id1'

Update

//수정
member.setAge(20);

jpa는 내부적으로 데이터에 대한 추적 기능이 있어 값이 수정되면 이를 추적해서 DB에 값을 반영한다.

UPDATE MEMBER 
SET AGE=20 
WHERE ID="id1'

DELETE

//삭제
em.remove(member);

remove 메소드를 이용해서 해당 객체를 제거한다.

DELETE FROM MEMBER 
WHERE ID='id1'

JPQL

만약 하나 이상의 회원 목록에 대한 조회를 하고자 하면 어떻게 해야될까? 한 명의 회원에 대해서는 find 메소드를 이용해서 구현하면 되었다.

List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();
        System.out.println("members.size=" + members.size());

엔티티 대상으로 검색해야하는데, DB에 대한 모든 데이터를 가져와서 엔티티로 변경한 다음 검색해야하는데, 이를 JPA만으로는 해결할 수 없다. 그래서 JPA는 객체 지향 쿼리언어인 JPQL(JAva Persistence Query Language)을 통한 검색 쿼리를 생성할 수 있도록 제공한다.

SQL과 유사한 특징이 있지만, 객체를 대상으로 쿼리를 만들 수 있다는 장점이 있다. 위에서 사용한 쿼리가 jpql로 구현한 코드이다. MEMBER 테이블이 아닌 Member 객체를 이용해서 쿼리를 만들었다.

select m from Member m

JPA에서는 JPQL을 보고 아래의 sql을 생성해낸다.

SELECT M.ID,M.NAME,M.AGE FROM MEMBER M

이 처럼 JPA는 JPA 자체적인 메소드 이외에도 객체 대상으로 쿼리를 수행할 수 있도록 JPQL을 제공한다.

References

book: 자바 ORM 표준 JPA 프로그래밍 -김영한 저

book_link

태그: ,

카테고리:

업데이트:

댓글남기기