본문 바로가기
Back-end/JPA 개념

2. JPA의 기본 구조와 기능

by devraphy 2022. 3. 23.

0. 개요

- JPA를 본격적으로 배우기에 앞서 기본적인 프로젝트 구성과 사용 방법에 대해서 간단히 알아보자. 

- 참고로 본 프로젝트는 Maven을 사용한 빌드 구성임을 알려드립니다. 

 

1. JPA 설정 파일

- 기본적인 설정 파일에 대해서 알아보자. 

 

a) pom.xml

- 프로젝트 빌드 설정 파일

- 기본적으로 Spring을 사용하기 때문에, Spring 프로젝트와 관련된 설정을 작성한다. 

- JPA를 사용하기 위해서 JPA의 구현체 중 하나를 import 한다.

- 아래의 예시 코드는 Hibernate 라이브러리를 설정하는 코드다.

 

<!-- JPA 하이버네이트 -->
 <dependency>
 <groupId>org.hibernate</groupId>
 <artifactId>hibernate-entitymanager</artifactId>
 <version>5.3.10.Final</version>
 </dependency>

 

b) persistence.xml

- JPA 설정 파일

- JPA의 기본적인 옵션을 설정하는 파일이다.

- persistence.xml은 /resources/META-INF/ 디렉터리에 위치하도록 설정한다.

 

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
             xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
    <persistence-unit name="hello">
        <properties>
            <!-- DB 설정 -->
            <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/~/jpashop"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>

            <!-- JPA 옵션 -->
            <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.hbm2ddl.auto" value="create" />
        </properties>
    </persistence-unit>
</persistence>

 

   → show_sql: commit 시점에서 JPA가 작성한 SQL을 콘솔에 출력한다.

   → format_sql: show_sql 옵션으로 출력되는 SQL을 읽기 쉽도록 formatting 한다.

   → use_sql_comments: JPA에 의해 작성된 SQL이 어떤 명령을 수행하는지, 어떤 객체를 사용하는지 콘솔에 출력한다. 

   → hbm2ddl.auto: 스키마 DDL을 검증한다. 설정 값에 따라 다양한 기능을 수행한다.

 

* DB 방언(hibernate.dialect 옵션)이란?

   → JPA의 CRUD를 사용하면 알아서 연결된 DB의 명령어로 변환해주는 기능 

   → 일반적으로 DB가 수행하는 기본적인 기능은 동일하지만, 다른 명령어를 사용한다.

   → 이에 따라 잘못된 DML, DDL이 작성되지 않도록, 연결된 DB가 무엇인지 JPA에게 알려줘야 한다.

   → 이 기능 덕분에 JPA는 특정 DB에 종속되지 않는다. 

 

2. JPA의 기본 구성

- 지금부터 설명할 내용은 나중에 다시 언급할 예정이다.

- 아래의 그림은 JPA가 어떤 과정을 거쳐서 시동되는지 설명한 것이다.

- 현시점에서는 이 흐름을 인지하도록 하자.

 

a) JPA 동작 방식

 

- JPA는 기본적으로 하나의 Persistence로부터 시작된다.

- JPA는 Transaction 단위로 DB와 네트워킹을 수행한다.

- Transaction을 기반으로 생성된 SQL을 DB에게 전달하는 역할을 Entity Manager가 수행한다.

- 그래서 하나의 Transaction을 수행할 때마다 하나의 Entity Manager를 필요로 한다.

- 여기서 알 수 있는 것은 Entity Manager가 Transaction과 동일한 라이프사이클을 가진다는 것이다.

- 더 자세한 이야기는 추후에 설명할 예정이다. 

 

b) Persistence Context

- 왠지 Spring의 Application Context(= Spring Container)와 어감이 비슷하지 않은가?

- 이해하기 쉽게 설명하자면 Spring Container처럼 JPA를 관리하는 역할이라고 생각해도 좋을 것 같다.

 

- 그러나 주의해야 할 점은, Persistence Context는 눈에 보이지 않는 논리적인 개념이라는 것이다.

- Persistence Context는 "객체(Entity)를 영구히 저장할 수 있는 환경"을 정의해놓은 논리적 개념이다.

- 즉, DB에 객체를 저장하는 내부 동작 과정 그 자체를 Persistence Context라고 한다. 

 

c) Entity Manager Factory

  → 웹 서버가 올라오는 시점에서 DB당 딱 1개의 객체만 생성된다. 

  → 마치 Spring Container처럼 JPA 객체를 관리한다.

 

d) Entity Manager

  → 고객의 요청이 올 때마다 생성되어 하나의 트랜잭션과 동일한 라이프사이클을 가지는 객체 

  → 마치 Servlet에서 고객의 요청마다 request 객체를 생성하고 응답과 함께 소멸되는 것처럼,

      고객의 요청에 의해 생성되고 트랜잭션 종료와 함께 소멸된다.

  → Entity Manager는 절대로 스레드끼리 공유하면 안 된다.

 

e) Transaction cycle

  → JPA를 이용한 모든 데이터 변경은 하나의 트랜잭션 사이클 안에서 실행된다. 

 

f) 코드로 보는 JPA의 동작 흐름

- 앞서 설명했듯이 JPA는 Transaction 단위로 하나의 사이클을 수행한다.

- 아래의 코드를 보면, 코드 또한 Transaction 단위로 비즈니스를 처리하도록 설계되어 있다.

- 현시점에서는 이 정도까지만 이해하자.

 

public class JpaMain {

    public static void main(String[] args) {
        // Entity Manager Factory 생성
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        
        // Entity Manager 생성
        EntityManager em = emf.createEntityManager();

        // 트랜잭션 생성
        EntityTransaction tx = em.getTransaction();

        // 트랜잭션 시작
        tx.begin();

        try {// 비즈니스 로직 구현부
        
            // commit 실행 
            tx.commit();
            
        } catch (Exception e) { // 예외 발생시 rollback 수행
        
            tx.rollback();
            
        } finally {
        
            // Transaction이 완료되면 반드시 EntityManager를 종료한다.
            em.close();
        }
        
        // Transaction이 완료되면 EntityManagerFactory도 종료한다.
        emf.close();
    }
}

 

3. Entity

- 간단하게 Entity의 구조와 역할에 대해서 알아보자. 

 

a) Entity란?

- JPA가 관리하는 대상, 객체

- DB 테이블을 Java 클래스로 구성한다.

 

b) Entity의 구조

- @Entity를 사용하여 해당 클래스가 특정 테이블을 객체화시킨 것이라 명시한다.

- @Id를 사용하여 DB 테이블의 기본 키(Primary key)를 명시한다. 

- 필수적으로 Getter, Setter를 생성한다. 

 

@Entity
public class Member {
    // JPA가 관리할 객체
    // DB의 테이블과 동일하게 구성

    @Id
    private Long id;
    private String name;

    // Getter & Setter
    public Long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

 

4. 비즈니스 로직 구현부 

- 앞서 설명했듯이, JPA는 Transaction 단위로 하나의 Cycle을 갖는다.

- 그러므로 하나의 Cycle 내부에서 비즈니스 로직을 수행하게 된다.

- 하나의 Transaction 단위로 비즈니스 로직을 수행하므로 트랜잭션의 시작과 끝을 설정한다.  

 

public class JpaMain {

    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();

        // 트랜잭션 생성
        EntityTransaction tr = em.getTransaction();

        // 트랜잭션 시작
        tr.begin();

        // 로직 구현부
        Member member = new Member();
        member.setId(1L);
        member.setName("HelloA");
        em.persist(member);

        tr.commit();

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

 

5. 실행 결과

- 위의 코드를 실행하면 아래의 사진과 같은 결과가 콘솔에 출력된다.

- 앞서 설명한 JPA 옵션에 의해, JPA가 어떤 동작(SQL)을 수행했는지 확인할 수 있다. 

 

 

a) persistence.xml 옵션

// 1. show_sql = SQL 쿼리를 출력한다.
<property name="hibernate.show_sql" value="true"/>

// 2. format_sql = 위의 사진처럼 SQL 쿼리를 정렬한다.
<property name="hibernate.format_sql" value="true"/>

// 3. use_sql_comments = /**/ 내부에 작성된 내용, 해당 쿼리가 수행한 동작을 설명한다.
<property name="hibernate.use_sql_comments" value="true"/>

 

6. 기본적인 예외 처리

- JPA는 Transaction 단위로 비즈니스 로직을 처리한다.

- 그러므로 예외가 발생하는 경우, Transaction 또한 오류가 있는 것이므로 rollback 해야 한다.

- 이러한 예외를 처리 수행하기 위해서 JPA는 try ~ catch 문을 사용한다. 

 

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 {
            // 로직 구현부
            Member member = new Member();
            member.setId(2L);
            member.setName("HelloB");

            em.persist(member);

            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
        emf.close();
    }
}

 

- 위의 코드 구조가 정석적인 JPA를 사용한 트랜잭션 하나의 동작 과정을 코드로 표현한 것이다.

- 그러나 사실 try ~ catch 또는 finally 구문에 들어간 내용들을 따로 작성할 필요가 없다. 

- 왜냐면 Spring을 사용하는 경우, Spring이 알아서 close() 해주기 때문이다.  

 

7. 기본적인 CRUD 메서드

- JPA를 사용함으로써 더 이상 SQL 중점적인 개발을 하지 않는다.

- 이 말은 개발자가 더 이상 SQL을 작성하는데 시간을 사용하지 않는다는 것이다.

 

- 그렇다면 JPA는 SQL을 없이 개발자가 CRUD를 사용할 수 있도록 하는 것일까?

- JPA 내부에는 이미 CRUD를 메서드로 만들어 놓았기 때문이다. 

- EntityManager 내부에는 기본적인 CRUD 외 다양한 쿼리 메서드를 보유하고 있다. 

- 개발자는 이를 가져다 호출하기만 하면 된다. 

 

- 이로써 JPA를 통해 OOP 중점적인 개발이 가능해진다. 

 

8. JPQL(Java Persistence Query Language)

- JPA를 사용하면 개발자는 따로 SQL을 작성하지 않고 OOP 중점적으로 개발한다.

- 그러나, 다양한 조건을 부여한 작업을 수행하는 경우 JPA 내부에 정의된 메서드가 기능적으로 부족한 경우가 있다.

- 그렇다면 직접 쿼리를 작성하여 사용할 수밖에 없는데 JPA에서는 쿼리를 작성할 수 없다. 

- 이때 사용할 수 있는 것이 JPQL이다.

 

- 일반적으로 쿼리(SQL)라고 하면 RDB에서 작성하는 방식을 생각한다. 

- RDB의 쿼리와 매우 유사한 문법을 가지지만, JPQL은 객체 중심적으로 쿼리를 작성한다.  

- SQL의 경우에는 DB 테이블을 조회하지만, JPQL은 객체(= Entity)를 조회하기 때문이다.

 

- JPQL의 최대 장점은 DB 방언을 변경하더라도 기존에 작성된 JPQL을 사용하는데 전혀 문제가 없다는 것이다. 

 

- 다음 예시를 간단하게 읽어보기를 바란다.

- 객체를 조회한다는 것이 어떤 것인지 가볍게 이해하도록 하자.

 

 List<Member> result = em.createQuery("select m from Member as m", Member.class).getResultList();
                    
for (Member member : result) {
   System.out.println("member.getName() = " + member.getName());
}

 

9. 마치며...

- 여기까지 JPA의 기본적인 구조와 기능에 대해서 알아보았다.

- 이와 같은 구조를 바탕으로, 다양한 기능이 합쳐져서 온전한 객체지향적 개발이 가능하게 된다.

 

- 이번 포스팅은 JPA 기본 개념에 대한 간략한 설명에 불과하다.

- 앞으로 하나씩 깊이 있게 배워보자.  

'Back-end > JPA 개념' 카테고리의 다른 글

5. Field(칼럼) Mapping  (0) 2022.03.28
4. Entity Mapping  (0) 2022.03.25
3. JPA의 내부구조와 동작  (0) 2022.03.24
1. JPA의 등장  (0) 2022.03.22
0. JPA를 사용하는 이유  (0) 2022.03.21

댓글