본문 바로가기
개인공부/JPA

2장 JPA 시작

by 응가1414 2024. 1. 20.

2장 JPA 시작

2장_JPA시작.md
0.02MB

 

2장_JPA시작.pdf
0.65MB

 

*.gradle 파일

plugins {
    id 'java'
}

group = 'org.example'
version = '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    testImplementation platform('org.junit:junit-bom:5.9.1')
    testImplementation 'org.junit.jupiter:junit-jupiter'

    // JPA, 하이버네이트
    // hibernate-core.jar, hibernate-jpa-2.1-api.jar 라이브러리도 함께 내려받는다.
    implementation 'org.hibernate:hibernate-entitymanager:4.3.10.Final'
    // H2 데이터베이스
    // H2 데이터베이스에 접속해야 하므로 h2 라이브러리도 지정한다.
    implementation 'com.h2database:h2:1.4.187'
}

test {
    useJUnitPlatform()
}

2.4 객체 매핑 시작

회원 테이블

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

회원 클래스에 JPA가 제공하는 매핑 에노테이션 추가

매핑 정보 회원 객체 회원 테이블
클래스와 테이블 Member MEMBER
기본키 id ID
필드와 컬럼 username NAME
필드와 컬럼 age AGE
package ch02.jpa.start1;

import javax.persistence.*;  //**

/**
 * 애플리케이션에서 사용할 회원 클래스 생성
 */
@Entity
@Table(name="MEMBER")
public class Member {

    // 기본 키(PK)에 매핑한다.
    // id 필드를 테이블의 ID 기본키 컬럼에 매핑한다.
    // 식별자 필드
    @Id
    @Column(name = "ID")
    private String id;

    // 필드를 컴럼에 매핑한다.
    @Column(name = "NAME")
    private String username;

    // 매핑 정보가 없는 필드
    // 필드 명이 age이르로 age컬럼으로 매핑
    private Integer age;

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

@Entity

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

@Table

  • 엔티티 클래스에 매핑할 테이블 정보를 알려준다.
  • 이 어노테이션을 생략하면 클래스 이름을 테이블 이름으로 매핑한다(더 정황히는 에니팉 이름을 사용한다. 엔티티 이름은 4.1절에서 설명)

@Id

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

@Colum

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

매핑 정보가 없는 필드

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

2.5 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">
        <!--  p78
           영속성 유닛에서 JPA설정이 시작된다.
           일반적으로 연결할 데이터베이스당 하나의 영속성 유닛을 등록
           영속성 유닛에서는 고유한 이름을 부여

           사용한 속성은 <properties>으로 속성 정의
       -->
              <!-- 엔티티 클래스 지정 -->
        <class>ch02.jpa.start1.Member</class>
        <properties>

            <!-- 필수 속성 -->
            <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
<!--            <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>-->
            <property name="javax.persistence.jdbc.user" value="sa"/>
<!--            <property name="javax.persistence.jdbc.user" value="root"/>-->
            <property name="javax.persistence.jdbc.password" value=""/>
<!--            <property name="javax.persistence.jdbc.password" value="qkrtmdcks1!"/>-->
            <property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:testdb"/>
<!--            <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost/study_jpa?characterEncoding=UTF-8"/>-->
            <!-- p79 데이터 베이스 방언(Dialect) 설정 -->
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />

            <!-- 옵션 -->
            <!-- p81 하이버네이트가 실행한 SQL을 출력한다. -->
            <property name="hibernate.show_sql" value="true" />
            <!-- p81 하이버네이트가 실행한 SQL을 출력할 때 보기 쉽게 정렬한다. -->
            <property name="hibernate.format_sql" value="true" />
            <!-- p81 쿼리를 출력할 때 주석도 함께 출력한다. -->
            <property name="hibernate.use_sql_comments" value="true" />
            <!-- p81 JPA 표준에 맞춘 새로운 키생성 전략을 사용한다. -->
            <property name="hibernate.id.new_generator_mappings" value="true" />
            <!-- Hibernate가 엔티티 클래스를 기반으로 데이터베이스 테이블을 자동으로 생성하려고 시도합니다.
            다음과 같이 설정할 수 있습니다: -->
            <!--<property name="hibernate.hbm2ddl.auto" value="create" />-->
        </properties>
    </persistence-unit>

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

속성 분석

하이버 네이트 속성 (가장 중요하다.)
  •             <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
    이름이 javax.persistence로 시작하는 속성은 JPA 표준 속성으로 특정 구현체에 종속 되지 않는다.하이버 네이트에서만 사용할 수 있다.
  • 반명에 hiberate로 시작하는 속성은 하이버네이트 전용 속성이므로

2.5.1 데이터베이스 방언 <DB 고유기능, 메서드 차이>

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

방언의 예

  • 데이터 타입
  • 다른 함수명
  • 페이징 처리 : MySQL은 LIMIT을 사용하지만 오라클은 ROWNUM을 사용한다.
  1. 이처럼 SQL 표준을 지키지 않거나 특정 데이터 베이스만의 고유한 기능을 JPA에는 방언(Dialect)라고 한다.
  2. 하이버네이트를 포함한 대부분의 JPA 구현체들은 이런 문제를 해결하려고 다양한 데이터베이스 방언 클래스를 제공한다.
  3. 개발자는 JPA가 제공하는 표준 문법에 맞추어 JPA를 사용하면 되고, 특정 데이터베이스에 의존적인 SQL은 데이터베이스 방언이 처리해준다.
  4. 따라서 데이터베이스가 변경되어도 애플리케이션 코드를 변경할 필요 없이 데이터베이스 방언만 교체하면 된다.
  • H2 : org.hibernate.dialect.H2Dialect
  • 오라클 10g : org.hibernate.dialect.Oracle10gDialect
  • MySQL : org.hibernate.dialect.MySQL5InnoDBDialect

참고

사용된 하이버네이트 전용 속성은 다음과 같다.

<!-- p81 하이버네이트가 실행한 SQL을 출력한다. -->
<property name="hibernate.show_sql" value="true" />

<!-- p81 하이버네이트가 실행한 SQL을 출력할 때 보기 쉽게 정렬한다. -->
<property name="hibernate.format_sql" value="true" />

<!-- p81 쿼리를 출력할 때 주석도 함께 출력한다. -->
<property name="hibernate.use_sql_comments" value="true" />

<!-- p81 JPA 표준에 맞춘 새로운 키생성 전략을 사용한다. -->
<property name="hibernate.id.new_generator_mappings" value="true" />

<!-- Hibernate가 엔티티 클래스를 기반으로 데이터베이스 테이블을 자동으로 생성하려고 시도합니다.
다음과 같이 설정할 수 있습니다: -->
<!--<property name="hibernate.hbm2ddl.auto" value="create" />-->
JPA 구현체들은 보통 에니팉 클래스를 자동으로 인식하지만, 환경에 따라 인식하지 못할 때도 있다.
그때는 persistence.xml에 <class>을 사용해 엔티티클래스를 지정한다.

<persistence-unit name="jpabook">

        <!-- 엔티티 클래스 지정 -->
        <class>ch02.jpa.start1.Member</class>

2.6 애플리케이션 개발

package ch02.jpa.start1;

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

/**
 * @author holyeye
 */
public class JpaMain {

    public static void main(String[] args) {

        //엔티티 매니저 팩토리 생성
        // p83. JPA를 시작하려면 우선 persistence.xml의 설정 정보를 사용해서 엔티티 매니저 팩토리 생성
        // 생성 비용이 크기 때문에 => 프로젝트 전반에 한번만 생성하고 공유해서 사용한다.
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");
        // p83. 엔티티 매니저는 등록/수정/삭제/조회를 할수 있다.
        // 엔티티 매니저는 내부에 데이터소스(데이터베이스 커넥션)를 유지 하면서 데이터베이스와 통신
        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(); //엔티티 매니저 팩토리 종료
    }

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

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

2.6.1 엔티티 매니저 설정

그림 2.12 엔티티 매니저 생성 과정

 

엔티티 매니저 팩토리 생성

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

엔티티 매니저 생성

// p83. 엔티티 매니저는 등록/수정/삭제/조회를 할수 있다.
// 엔티티 매니저는 내부에 데이터소스(데이터베이스 커넥션)를 유지 하면서 데이터베이스와 통신
EntityManager em = emf.createEntityManager(); //엔티티 매니저 생성
  • 엔티티 매니저 팩토리에서 엔티티 매니저를 생성한다.
  • JPA의 기능 대부분은 이 엔티티 매니저가 제공한다.
  • 엔티티 매니저를 사용해서 엔티티를 데이터베이스에 등록/수정/삭제/조회할 수 있다.
  • 엔티티 매니저는 내부에 데이터소스(데이터베이스 커넥션)을 유지하면서 데이터베이스와 통신한다.
  • 참고로 엠티티 매니저는 데이터베이스 커넥션과 밀접한 관계가 있으므로 스레드간에 공유하거나 재사용하면 안 된다.

종료

  • 마지막으로 사용이 끝난 엔티티 매니저는 다음처럼 반드시 종료해야한다.
  • em.close(); // 엔티티 매니저 종료
  • 애플리케이션을 종료할 때 엔티티 매니저 팩토리도 다음처럼 종료해야 한다.
  • emf.close(); // 엔티티 매니저 팩토리 종료

2.6.2 트랜잭션 관리

  • JPA을 사용하면 항상 트랜잭션 안에서 데이터를 변경해야 한다.
  • 트랜젝션 없이 데이터를 변경하면 예외가 발생한다.
  • 트랜잭션을 시작하려면 엔티티 매니저(em)에서 트랜잭션 API를 받아와야 한다.
  • ...
    EntityTransaction tx = em.getTransaction(); //트랜잭션 기능 획득
    
            try {
    
    
          tx.begin(); //트랜잭션 시작
          logic(em);  //비즈니스 로직
          tx.commit();//트랜잭션 커밋

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

...




## 2.6.3 비즈니스 로직 <emd을 통해 CRUD 관리>

- 회원 엔티티를 하나 생성한 다음 엔티티 매니저를 통해 데이터베이스에 등록, 수정, 삭제, 조회한다.

```java
    public static void logic(EntityManager em) {
        String id = "id1";
        Member member = new Member();
        member.setId(id);
        member.setUsername("지한");
        member.setAge(2);

        //등록
        // p85. 저장을 하는 메서드, 매개 변수에 저장할 엔티티를 넘겨준다.
        em.persist(member);

        //수정
        // p85. 수정자 메소드(set)메소드 호출
        member.setAge(20);

        //한 건 조회
        // p86. @Id로 데이터베이스 테이블의 기본키와 매핑한 식별자 값으로 엔티티 하나를 조회
        Member findMember = em.find(Member.class, id);
        System.out.println("findMember=" + findMember.getUsername() + ", age=" + findMember.getAge());

        //목록 조회
        // p87. JPQL
        // 하나 이상의 회원 목록을 조회하는 다음 코드를 자세히 살펴보자.
        // JPQL을 사용하기 위해서는 em.createQuery(JPQL, 반환_타입.class) 메소드를 실행하서
        // query.getResultList()을 실행한다.
        TypedQuery<Member> query = em.createQuery("select m from Member m", Member.class);
        List<Member> members = query.getResultList();
        System.out.println("members.size=" + members.size());

        //삭제
        em.remove(member);
    }
  • 비즈니스 로직을 보면 등록, 수정, 삭제, 조회 작업이 엔티티 매니저(em)를 통해서 수행되는 것을 알수 있다.
  • 엔티티 매니저는 객체를 저장하는 가상의 데이터베이스 처럼 보인다.
  • 먼저 등록, 수정, 삭제 코드를 분석해보다.

등록

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

        //등록
        // p85. 저장을 하는 메서드, 매개 변수에 저장할 엔티티를 넘겨준다.
        em.persist(member);
  • Persist() 메소드에 저장할 엔티티를 넘겨주면 된다.
  • em.persist(member)를 실행해서 엔티티를 저장했다.

수정

        //수정
        // p85. 수정자 메소드(set)메소드 호출
        member.setAge(20);
  • 엔티티를 수정한 후에 수정 내용을 반영하려면 em.update() 같은 메소드를 호출해야 할 것 같은데 단순히 엔티티의 값만 변경 했다.
  • member.setAge(20)처럼 엔티티의 값만 변경하면 다음과 같은 UPDATE SQL을 생성해서 데이터베이스에 값을 변경한다.

삭제

        //삭제
        em.remove(member);
  • 엔티티를 삭제하려면 엔티티 매니저의 remove() 메소드에 삭제하려는 엔티티를 넘겨준다.

한 건 조회

        //한 건 조회
        // p86. @Id로 데이터베이스 테이블의 기본키와 매핑한 식별자 값으로 엔티티 하나를 조회
        Member findMember = em.find(Member.class, id);
        System.out.println("findMember=" + findMember.getUsername() + ", age=" + findMember.getAge());
  • find() 메소드는 조회할 엔티티 타입과
  • @Id로 데이터베이스 테이블의 기본키와 매핑한 식별자 값으로 엔티티 하나를 조회하는 가장 단순한 조회 메소드다.

JPQL

        //목록 조회
        // p87. JPQL
        // 하나 이상의 회원 목록을 조회하는 다음 코드를 자세히 살펴보자.
        // JPQL을 사용하기 위해서는 em.createQuery(JPQL, 반환_타입.class) 메소드를 실행하서
        // query.getResultList()을 실행한다.
        TypedQuery<Member> query = em.createQuery("select m from Member m", Member.class);
        List<Member> members = query.getResultList();
        System.out.println("members.size=" + members.size());
  • 엔티티 객체를 중심으로 개발하고 데이터베이스에 대한 처리는 JPA에 맡겨야 한다.
  • JPA는 엔티티 객체를 중심으로 개발하므로 검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 검색해야한다.
  • 테이블이 아닌 엔티티 객체를 대상으로 검색하려면 데이터베이스의 모든 데이터를 애플리케이션으로 불러와서 엔티티 객체로 변경항 다음 검색해야하는데
  • 이는 불가능하다.
  • JPA는 JPQL(java Persistence Query Language)이라는 쿼리 언어로 이런 문제를 해결한다.
  • JPA는 SQL을 추상화한 JPQL이라는 객체지향 쿼리 언어를 제공한다.
- JPQL은 엔티티 객체를 대상으로 쿼리한다. 쉽게 이야기해서 클래스와 필드를 대상으로 쿼리한다.
- SQL은 데이터베이스 테이블을 대항으로 쿼리 한다.
  • select m from Member m이 바로 JPQL이다. 여기서 from Member는 회원 엔티티 객체를 말하는 것이다.
  • JPQL은 데이터베이스 테이블을 전혀 알지 못한다.
  • JPQL을 사용하려면 먼저 em.createQuery(JPQL, 반환 타입) 메소드를 실행해서 쿼리 객체를 생성한 후 쿼리 객체의 getResultList() 메소드를 호출하면 된다.

'개인공부 > JPA' 카테고리의 다른 글

6장 다양한 연관관계 매핑  (1) 2024.02.05
5장 연관관계 매핑 기초  (1) 2024.02.05
4장 엔티티 매핑  (1) 2024.01.30
3장 영속성 관리  (1) 2024.01.23
1장 JPA 소개  (0) 2024.01.20