자바 ORM 표준 JPA 프로그래밍

2. JPA 시작

Featured image

라이브러리와 의존성 관계

springboot에서는 spring-boot-starter-data-jpa 의존성을 추가하면 아래 의존성들도 같이 추가된다.

jakarta.persistence-api vs javax.persistence-api

2019년 부터 javax.persistence-api가 jakarata.persistence-api로 명칭이 변경되었다.

The Java Persistence API (JPA), in 2019 renamed to Jakarta Persistence, is a Java application programming interface specification that describes the management of relational data in applications using Java Platform, Standard Edition and Java Platform, Enterprise Edition/Jakarta EE.

참고 : https://en.wikipedia.org/wiki/Java_Persistence_API

객체 매핑

회원 클래스 생성

    public class Member {
        private String id;          // 기본키
        private String username;
        private Integer age;
    }

매핑 정보

매핑 정보 회원 객체 회원 테이블
클래스와 테이블 member MEMBER
기본 키 id ID
필드와 컬럼 username NAME
필드와 컬럼 age AGE

매핑 어노테이션 추가

import javax.persistence.

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

        @Id @Column(name="ID")
        private String id;          // 기본키

        @Column(name="NAME")
        private String username;

        // 매핑 정보가 없는 필드
        private Integer age;
    }

@Entity, @Table, @Column 이 매핑정보다. JPA는 매핑 어노테이션을 분석해 어떤 객체가 어떤 테이블과 관계가 있는지 알아낸다.

@Entity

해당 어노테이션이 선언된 클래스를 테이블과 매핑한다고 JPA에 알려준다. 해당 어노테이션이 선언된 클래스를 엔티티 클래스 라고 한다.

@Table

엔티티 클래스에 매핑할 테이블 정보를 알려준다. 해당 어노테이션을 생략하게 되면 클래스 이름을 테이블 이름으로 매핑한다.(더 정확히는 엔티티 이름을 사용하게 된다.)

@Id

엔티티 클래스의 필드를 테이블의 기본 키에 매핑한다. @Id가 선언된 필드를 식별자 필드라고 한다.

@Column

엔티티 필드를 테이블 컬럼에 매핑한다.

매핑 정보가 없는 필드

매핑 어노테이션을 생략하게 되면 필드명을 컬럼명으로 매핑한다.

대소문자를 구분하는 데이터베이스를 사용하게 되면 @Column(name=”AGE”)와 같이 명시적으로 매핑해야 한다.

데이터베이스 방언

SQL표준을 지키지 않거나 특정 데이터베이스만의 고유한 기능을 말한다. 애플리케이션 개발자가 특정 데이터베이스에 종속되는 기능을 많이 사용하면 나중에 데이터베이스를 교체하기가 어렵게 된다. 하이버네이트를 포함한 대부분의 JPA 구현체들을 이러한 문제를 해결하기 위해 다양한 데이터베이스 방언 클래스를 제공한다. JPA Dialect

JPA yml 설정 예

jpa:
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
    hibernate:
      ddl-auto: none
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL5InnoDBDialect
        format_sql: true
        physical_naming_strategy: com.ncp.common.hibernate.CamelCaseColumnStrategy
        implicit_naming_strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImpl
        use_sql_comments: true
        show_seq: true
    database: mysql
    open-in-view: false
    show-sql: false
    generate-ddl: false

참고 : https://kingbbode.tistory.com/27

하이버네이트 전용 속성

애플리케이션 코드

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

public class JpaMain {
    public static void main(String[] args) {
        //[엔티티 매니저 팩토리] - 생성
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("project name");
        
        //[엔티티 매니저] - 생성
        EntityManager em = emf.createEntityManager();

        //[트랜잭션] - 획득
        EntityTransaction tx = em.getTransaction();

        try {
            tx.begin();     //[트랜잭션] - 시작
            logic(em);      //비즈니스 로직 실행
            tx.commit();    //[트랜잭션] - 커밋
        } catch (Exception e) {
            tx.rollback();  //[트랜잭션] - 롤백
        } finally {
            em.close();     //[엔티티 매니저] - 종료
        }
        emf.close();        //[엔티티 매니저 팩토리] - 종료
    }

    private static void logi(EntityManager em) {...}
}    

코드는 크게 3가지로 나뉘어 있다.

엔티티 매니저 설정

엔티티 매니저 팩토리 생성

spring boot라면 application.yml의 설정 정보를 통해 엔티티 매니저 팩토리를 생성한다. 설정 정보를 통해 JPA를 동작시키기 위한 기반 객체를 만들고 JPA 구현체 따라서 데이터베이스 커넥션 풀도 생성하므로 엔티티 매니저 팩토리를 생성하는 비용이 아주 크다. 따라서 엔티티 매니저 팩토리는 애플리케이션 전체에서 한 번만 생성하고 공유해서 사용해야 한다.

엔티티 매니저 생성

엔티티 매니저 팩토리에서 엔티티 매니저를 생성한다. 엔티티 매니저를 사용하여 엔티티를 데이터베이스에 CRUD할 수 있다. 엔티티 매니저는 데이터베이스 커넥션과 밀접한 관계가 있으므로 스레드 간에 공유하거나 재사용하면 안 된다.

종료

사용이 끝난 엔티티 매니저와 엔티티 매니저 팩토리는 반드시 종료해야 한다.

트랜잭션 관리

JPA를 사용하여 데이터를 변경하려면 항상 트랜잭션 안에서 이뤄져야 한다. 엔티티 메니저에서 트랜잭션 API를 받아와서 사용한다.

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

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

비즈니스 로직

    private static void logic(EntityManager em) {
        
        String id = "id";
        Member member = new Member();
        member.setId(id);
        member.setUsername("재두");
        member.setAge(31);

        //등록
        em.persist(member);

        //수정
        member.setAge(20);

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

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

        //삭제
        em.remove(member);

    }

등록

회원 엔티티 생성 후 em.persist(member); 를 실행하여 엔티티를 저장한다. JPA는 회원 엔티티 매핑 정보(어노테이션)을 분석해 (1차 캐시에 없다면) 아래 SQL을 생성하여 데이터베이스에 전달한다.

    INSERT INTO MEMBER (ID, NAME, AGE) VALUES ('id', '재두', '31');

수정

JPA는 어떤 엔티티가 변경되었는지 추적하는 기능이 있어, 기존에 데이터베이스에 존재하는 값을 변경하는 것이라면 아래와 같은 수정 SQL을 생성하여 데이터베이스에 전달한다.

    UPDATE MEMBER SET AGE=20 WHERE ID = 'id';

삭제

엔티티를 삭제하려면 엔티티 매니저의 remove() 메소드를 실행하면 된다. 그러면 JPA는 삭제 SQL을 생성하여 데이터베이스에 전달한다.

    DELETE FROM MEMGER WHERE ID = 'id';

한 건 조회

find() 메소드를 실행하면 JPA에서는 조회할 엔티티 타입과 @Id 어노테이션으로 데이터베이스 테이블의 기본 키와 매핑한 식별자 값을 찾아 (1차 캐시에 해당 값이 없다면) 하나의 엔티티를 조회하는 SQL을 생성하여 데이터베이스에 전달한다. 그리고 조회한 결과 값으로 엔티티를 생성하여 반환한다.

목록 조회

JPA를 사용하면 개발자는 엔티티 객체 중심으로 개발하고 데이터베이스에 대한 처리는 JPA에 맡겨야 한다. 위 기능들은 모두 SQL을 사용하지 않았으나, 목록 조회와 같은 검색 쿼리는 모든 엔티티 객체를 대상으로 검색해야 하는데 SQL은 테이블을 대상으로 쿼리를 한다. 이 때 JPQL을 사용해야 한다.

JPQL(Java Persistence Query Language)

SQL을 추상화한 객체지향 쿼리 언어로써 엔티티 객체를 대상으로 쿼리한다. 즉, 클래스와 필드를 대상으로 쿼리한다.

정리

JPA를 사용함으로써 반복적인 JDBC API와 결과 값 매핑을 처리해줬기 때문에 코드의 양이 상당히 줄어들었음을 알 수 있었다.