Avatar 창조적이고 생산적이고 싶은 개발자

#1 JPA 프로그래밍

persistence.xml 설정

JPA에 필요한 설정 정보를 관리하는 파일

// resources/META-INF/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" />
        </properties>
    </persistence-unit>

</persistence>

persistence-unit

<persistence-unit name="jpabook">
  • 일반적으로 연결할 데이터베이스당 하나의 영속성 유닛을 등록
  • 영속성 유닛에는 고유한 이름을 부여해야 함
  • 각 영속성 유닛마다 속성이 추가됨

jpa 표준 속성

  • javax.persistence로 시작하는 속성은 JPA 표준 속성
  • 특정 구현체에 종속되지 않음

하이버네이트 속성

  • hibernate로 시작하는 속성
  • 특정 구현체에 종속되는 속성들(여기서는 하이버네이트)

JPA 시작하기

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

public class JpaMain {

    public static void main(String[] args) {

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

        
        EntityManager em = emf.createEntityManager();

        // (2)
        EntityTransaction tx = em.getTransaction();

        try {
            tx.begin();
            // (3)
            logic(em);
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }

        emf.close();
    }

    private static void logic(EntityManager em) {
        /// ...
    }
}

1. 엔티티 매니저 설정

엔티티 매니저 팩토리 생성

persistence.xml에서 설정된 영속성 유닛을 Persistence 클래스를 사용해 엔티티 매니저 팩토리를 생성한다.

// jpabook 영속성 유닛에 대한 팩토리 생성
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");
  • jpa를 동작시키기 위한 기반 객체를 생성
  • 구현체에 따라서는 데이터베이스 커넥션 풀도 생성
  • 생성에 드는 비용이 아주 크므로 어플리케이션 전체에서 딱 한 번만 생성하고 공유해서 사용

앤티티 매니저 생성

EntityManager em = emf.createEntityManager();
  • JPA의 대부분의 기능을 제공하는 역할
  • 엔티티 매니저를 사용해서 CRUD처리가 가능
  • 데이터 베이스 커넥션과 밀접한 관계가 있으므로 스레드간의 공유하거나 재사용하면 주의

종료

em.close();
emf.close();
  • 사용이 끝난 인스턴스는 반드시 종료해야 한다.

2. 트랜잭션 관리

JPA를 사용하면 항상 트랙잭션 안에서 데이터를 변경해야 한다. 트랜잭션 없이 변경하면 예외가 발생하므로 주의해야 한다.

EntityTransaction tx = em.getTransaction();
try {
    tx.begin(); // 트랜잭션 시작
    logic(em); // 로직 실행
    tx.commit(); // 비즈니스 로직 처리 완료 후 트랜잭션 커밋
} catch (Exception e) {
    tx.rollback(); // 예외 발생 시 트랜잭션 롤백
}

3. 비즈니스 로직

private 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());

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

        System.out.println("member.size=" + members.size());

        // 삭제
        em.remove(member);
    }

    /*
        출력 결과
        findMember=이다, age=20
        member.size=1
    */

등록

엔티티를 저장하려면 엔티티 매니저의 persist() 메소드에 저장할 엔티티를 넘겨주면 된다. JPA는 엔티티의 매핑 정보(어노테이션)을 분석해서 SQL을 반들어 데이터베이스에 전달한다.

INSERT INTO MEMBER(ID, NAME, AGE) VALUES ('id1', '이다', 2)

수정

엔티티를 수정한 후에 수정 내용을 반영하려면 em.update() 같은 메소드를 호출해야 할 것 같지만 단순히 엔티티의 값만 변경했다. JPA(정확히는 엔티티 매니저)는 persist된 엔티티를 감지하고 있다가 변경되면 알아서 값을 변경한다.

UPDATE MEMBER
    SET AGE = 20, NAME = '이다'
WHERE ID = 'id1'

삭제

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

DELETE FROM MEMBER WHERE ID = 'id1'

한 건 조회

find() 메소드는 조회할 엔티티 타입과 엔티티의 @Id 어노테이션을 이용해 테이블의 기본 키와 매핑한 식별자 값으로 엔티티 하나를 조회한다.

SELECT * FROM MEMBER WHERE ID = 'id1'

목록 조회

앞에서 살펴본 예제들은 SQL을 전혀 사용하지 않았다. 문제는 검색 쿼리인데 JPA는 엔티티 중심으로 개발하므로 검색을 할 떄도 테이블이 아닌 엔티티 객체를 대상으로 검색해야한다. 여기서 사용하는 쿼리 언어를 JPQLJava Persistence Query Language 이라고 한다. JPQL은 SQL과 문법이 거의 유사하고 SQL를 추상화한 문법이다(Dialect가 알아서 다 해준다)

일반 SQL과의 가장 큰 차이점은 2가지 정도인데

  1. JPQL은 엔티티 객체를 대상으로 쿼리한다. 쉽게 이야기해서 클래스와 필드를 대상으로 쿼리
  2. SQL은 데이터베이스 테이블을 대상으로 쿼리

이게 무슨말이냐면 아래 코드를 보면서 얘기해보자

TypedQuery<Member> query = em.createQuery("select m from Member m", Member.class);

여기서 쿼리 문자열 중 Member는 실제 데이터베이스의 Member 테이블이 아닌 Member class다. JPQL은 데이터베이스 테이블과는 전혀 상관이 없다.

쉽게 말해서 엔티티에서 필드명을 username 이라고 정의하고 @Column(“name”)을 이용해 데이터베이스 컬럼명을 name으로 지정하게 되었을 때 JPQL은 아래와 같다

em.createQuery("select m from Member m where m.username='이다현'");

Hibernate: 
    /* select
        m 
    from
        Member m 
    where
        m.username='이다' */ select
            member0_.ID as ID1_0_,
            member0_.age as age2_0_,
            member0_.NAME as NAME3_0_ 
        from
            MEMBER member0_ 
        where
            member0_.NAME='이다'

Member 클래스의 username필드를 조회하는 JPQL을 작성했지만 실제 쿼리(SQL)은 데이터베이스의 테이블의 name을 조건으로 쿼리한다.