[JPA/김영한] 02. JPA 시작

Ogu·2024년 3월 7일
0
post-thumbnail

이 시리즈는 김영한님의 자바 ORM 표준 JPA 프로그래밍 책을 학습하고 기록하기 위한 포스팅입니다.
해당 2장에서는 앞의 이클립스 설치 및 프로젝트 불러오기, H2 DB 설치 과정은 생략하고 그 이후부터 정리하도록 하겠습니다.

해당 책과 시리즈에서는 빌드 도구로 Maven을 사용합니다.

라이브러리와 프로젝트 구조

필요한 모든 라이브러리를 직접 내려받아 관리하기는 매우 어렵고 비효율적입니다. 자바 어플리케이션들은 대부분 따라서 Maven이나 Gradle과 같은 도구를 사용합니다.

JPA 구현체로 하이버네이트를 사용하기 위한 핵심 라이브러리는 아래와 같습니다.

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

메이븐과 사용 라이브러리 관리

Maven은 라이브러리 관리 기능빌드 기능을 제공하는 도구입니다. pom.xml에 사용할 라이브러리들을 import 하면 자동으로 내려받아서 관리해줍니다. 물론 인터넷 환경이어야 합니다.

📟 라이브러리 관리 기능
자바 애플리케이션에서는 .jar 파일로 된 여러 라이브러리를 사용합니다. 과거에는 직접 내려받아 관리했지만, 메이븐은 사용할 라이브러리 이름, 버전만 명시하면 자동으로 내려받아 관리해줍니다.

📟 빌드 기능
애플리케이션을 직접 빌드하는 것은 굉장히 고된 작업인데, 메이븐은 애플리케이션을 빌드하는 표준화된 방법을 제공합니다.

메이븐 설정 파일 pom.xml

메이븐 설정 파일인 pom.xml의 예시를 보면 다음과 같습니다.

<?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>

특징을 보면 다음과 같습니다.
1. <dependencies>에 사용할 라이브러리들을 지정합니다.
2. <dependency> 태그 안에 groupId + artifactId + version 을 명시하면 라이브러리(jar 파일)을 메이븐 공식 저장소에서 내려받아 추가해줍니다.

JPA, 하이버네이트 (hibernate-entitymanager)

JPA 표준과 하이버네이트를 포함하는 라이브러리, hibernate-entitymanger를 라이브러리로 지정하면 다음 중요 라이브러리도 함께 내려받습니다.

  • hibernate-core.jar
  • hiberante-jpa-2.1-api.jar

객체 매핑

회원 테이블 생성

아래와 같이 SQL을 작성후 실행하여 회원 테이블을 생성합니다.

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

회원 클래스 생성

애플리케이션에서 사용할 회원 클래스를 생성합니다.

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;
    }
}

JPA를 사용하려면 가장 먼저 회원 클래스와 회원 테이블을 매핑해야 합니다.
회원 클래스의 매핑 정보는 다음과 같습니다.

매핑 정보회원 객체회원 테이블
클래스와 테이블MemberMEMBER
기본 키idID
필드와 컬럼usernameNAME
필드와 컬럼ageAGE

회원 클래스에 JPA 매핑 어노테이션 추가

package jpabook.start;

import javax.persistence.*;

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

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

    @Column(name = "NAME")
    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;
    }
}

  • 그림 출처 : https://demoversion.tistory.com/94

  • @Entity
    테이블과 매핑한다고 JPA에 알려줍니다. @Entity가 사용된 클래스를 엔티티 클래스라고 합니다.

  • @Table
    엔티티 클래스에 매핑할 테이블 정보를 알려줍니다. name 속성을 사용해서 Member 엔티티를 MEMBER 테이블에 매핑할 수 있습니다.
    해당 어노테이션을 생략하면 클래스 이름을 그대로 테이블 이름으로 매핑합니다.

  • @Id
    엔티티 클래스의 필드를 테이블의 기본 키(Primary Key)에 매핑합니다.
    @Id가 사용된 필드를 식별자 필드라고 합니다.

  • @Column
    필드를 컬럼에 매핑한다.
    "name 속성"을 사용해서 Member 엔티티의 username 필드를 테이블의 NAME 컬럼에 매핑
    매핑 정보가 없는 필드

  • 매핑 어노테이션을 생략
    매핑 어노테이션을 생략하면 필드명을 사용해서 컬럼명으로 매핑합니다.
    만약 대소문자를 구분하는 데이터베이스를 사용하면 @Column(name="AGE")처럼 명시적으로 매핑해야 한다.

JPA 어노테이션 패키지
JPA 어노테이션 패키지는 자바 17 버전 이전은 javax.persistence, 자바 17 이후는 jakarta.persistence 입니다.

persistence.xml 설정

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

JPA 환경설정 파일 persistence.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>
  • persistence : 설정 파일은 persistence로 시작하는데, XML 네임스페이스와 사용할 버전을 지정합니다.
  • <persistence-unit name="jpabook"> :JPA 설정은 영속성 유닛이라는 것 부터 시작합니다. 일반적으로 연결할 데이터베이스 하나당 하나의 영속성 유닛을 등록합니다.
    그리고 영속성 유닛에는 고유한 이름을 부여해야 합니다. (여기서는 jpabook이라는 이름을 부여합니다.)

JPA 표준 속성

  • javax.persistence.jdbc.driver : JDBC 드라이버
  • javax.persistence.jdbc.user : 데이터베이스 접속 아이디
  • javax.persistence.jdbc.password : 데이터베이스 접속 비밀번호
  • javax.persistence.jdbc.url : 데이터베이스 접속 URL

하이버네이트 설정

  • hibernate.dialect : 데이터베이스 방언 설정

🍑 java.persistence vs hivernate

  • javax.persistence 로 시작하는 속성은 JPA 표준 속성으로, 특정 구현체에 종속되지 않음
  • hibernate로 시작하는 속성은 하이버네이트 전용 속성

데이터베이스 방언

JPA는 특정 데이터베이스에 종속적이지 않은 기술으로, 다른 데이터베이스로 손쉽게 교체할 수 있습니다.
하지만 각 DB가 제공하는 SQl 문법과 함수는 조금씩 다릅니다.

DB마다의 차이점

SQl 표준을 지키지 않거나 특정 DB만의 고유한 기능을 JPA에서는 방언이라고 합니다.

예를 들어 다음과 같은 차이점이 있습니다.

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

개발자는 단지 JPA가 제공하는 표준 문법에 맞춰 JPA를 사용하면 DB에 의존적인 SQL은 데이터베이스 방언이 처리해줍니다.

하이버네이트 전용 속성

하이버네이트 전용 속성으로 아래와 같은 속성들을 사용할 수 있습니다.
show_sqlformat_sql은 자주 사용되는 속성이므로 기억하도록 합니다.

  • hibernate.show_sql : 실행한 SQL을 출력
  • hibernate.format_sql : SQL을 보기 좋게 정렬함
  • hibernate.use_sql_comments : 쿼리 출력 시 주석도 함께 출력
  • hibernate.id.new_generator_mappings : JPA 표준에 맞는 새로운 키 생성 전략을 사용

애플리케이션 개발

객체 매핑과 persistence.xml 로 JPA 설정을 완료했다면, 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) {
            e.printStackTrace();
            tx.rollback(); // [트랜잭션] - 롤백
        } finally {
            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);

        //수정
        member.setAge(20);

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

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

        //삭제
        em.remove(member);

    }
}

엔티티 매니저 설정

엔티티 매니저의 생성 과정은 다음과 같다.

1. 엔티티 매니저 팩토리 생성

JPA를 시작하기 위해서 persistence.xml의 설정 정보를 사용해서 엔티티 매니저 팩토리를 생성해야 합니다.
이때, Persistence 클래스 사용합니다.

  • Persistence 클래스 : 엔티티 매니저 팩토리를 생성해서 JPA를 사용할 수 있게 준비.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");

META-INF/persistence.xml에서 이름이 "jpabook"인 영속성 유닛을 찾아서 엔티티 매니저 팩토리를 생성합니다.

이 때, 설정 정보 읽어서 JPA 동작을 위한 기반 객체를 생성하고, JPA 구현체에 따라 데이터베이스 커넥션 풀도 생성합니다.

💡 따라서 엔티티 매니저 생성 비용은 아주 크므로 어플리케이션 전체에 딱 한 번만 생성하고 공유해서 사용해야 합니다.

2. 엔티티 매니저 생성

EntityManager em = emf.createEntityManager();

엔티티 매니저 팩토리에서 엔티티 매니저를 생성합니다. JPA의 기능 대부분은 엔티티 매니저가 제공하며, 엔티티 매니저를 사용해서 엔티티를 데이터베이스에 등록/수정/삭제/조회할 수 있습니다.
💡 엔티티 매니저는 데이터베이스 커넥션과 밀접한 관계가 있으므로 스레드간에 공유하거나 재사용하면 안됩니다.

3. 종료

사용이 끝난 엔티티 매니저는 반드시 종료해야 합니다.

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

어플리케이션을 종료할 때 엔티티 매니저 팩토리도 같이 종료합니다.

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

트랜잭션 관리

JPA를 사용하면 항상 트랜잭션 안에서 데이터를 변경해야 합니다.
트랜잭션 없이 데이터를 변경하면 예외가 발생합니다.

트랜잭션을 시작하기 위해서는 엔티티 매니저에서 트랜잭션 API를 받아와야 합니다.

트랜잭션 코드

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

try {

    tx.begin(); // 트랜잭션 시작
    logic(em);  // 비즈니스 로직
    tx.commit();// 트랜잭션 커밋

} catch (Exception e) {
    tx.rollback(); // 예외 발생 시 트랜잭션 롤백
}

트랜잭션 API를 사용해 비즈니스 로직이 정상 동작하면 트랜잭션을 커밋(commit)하고, 예외가 발생하면 롤백(rollback)합니다.

비즈니스 로직

엔티티 매니저를 통해 데이터베이스에 CRUD를 합니다.

String id = "id1";
Member member = new Member();
member.setId(id);
member.setUsername("지한");
member.setAge(2);

//등록
em.persist(member);

//수정
member.setAge(20);

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

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

//삭제
em.remove(member);

등록

엔티티를 저장하기 위해서는 엔티티 매니저의 persist() 메서드에 저장할 엔티티를 넘겨줍니다.

수정

수정은 조금 특이합니다. 단순히 엔티티의 값을 변경하는데요, JPA에서 따로 udpate()메서드를 제공하지 않습니다.
JPA는 어떤 엔티티가 변경되었는지 추적하는 기능인 Dirty Checking을 제공하기 때문입니다.

따라서 member.setAge(20) 처럼 엔티티의 값만 변경해도 UPDATE SQL을 생성하여 DB에 값을 변경합니다.

삭제

엔티티를 삭제하기 위해서는 엔티티 매니저의 remove() 메서드에 삭제하려는 엔티티를 넘겨줍니다.

한 건 조회

find() 메서드는 조회할 엔티티 타입과 @Id로 데이터베이스 테이블의 기본 키와 매핑한 식별자 값으로 엔티티 하나를 조회하는 가장 단순한 조회 메서드입니다.

해당 메서드를 호출하면 SELECT SQL을 생성해 DB에서 결과를 조회하고, 그 결과값으로 엔티티를 생성해 반환합니다.

JPQL

JPA를 사용하면 엔티티 객체를 중심으로 개발하고, DB 관련 처리는 JPA에 맡기게 됩니다.
하지만 문제는 검색 쿼리인데요, 테이블이 아닌 엔티티 객체를 대상으로 검색하려면 데이터베이스의 모든 데이터를 어플리케이션으로 불러와 엔티티 객체로 변경해서 검색해야 하는데 이는 사실상 불가능합니다.

따라서 필요한 데이터만 DB에서 불러오기 위해서 검색 조건이 포함된 SQL을 사용해야 합니다.
JPA는 이러한 문제를 해결하기 위해 JPQL(Java Persistence Quert Language)이라는 SQL을 추상화한 쿼리 언어를 제공합니다.

JPQL은 데이터베이스 테이블을 전혀 알지 못합니다.

JPQL vs SQL

  • JPQL : 엔티티 객체 대상으로 쿼리 (클래스와 필드)
  • SQL : 데이터베이스 테이블 대상으로 쿼리

예제

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

위의 코드로 생성된 SQL

SELECT M.ID, M.NAME, M.AGE FROM MEMBER M
profile
私はゲームと日本が好きなBackend Developer志望生のOguです🐤🐤

0개의 댓글