JPA(Java Persistence API) 프로그래밍 - JPA 시작하기

u-nij·2022년 5월 7일
0

JPA 프로그래밍

목록 보기
1/10
post-thumbnail

이 글은 김영한님의 자바 ORM 표준 JPA 프로그래밍 - 기본편 강의를 듣고 정리한 글입니다.

SQL 중심적인 개발의 문제점

  • SQL에 의존적인 개발을 피하기 어렵다
  • 패러다임의 불일치 : 객체 vs 관계형 데이터베이스
    • 상속
    • 연관관계
    • 데이터 타입
    • 데이터 식별 방법
  • 객체답게 모델링 할수록 매핑 작업만 들어남

→ 객체를 자바 컬렉션에 저장하듯이 DB에 저장할 수 있는 방법 : JPA(Java Persistence API)

JPA(Java Persistence API)

자바 진영의 ORM 기술 표준, 패러다임 불일치 해결

ORM(Object-relational mapping 객체 관계 매핑)
객체는 객체대로 설계, 관계형 데이터베이스는 관계형 데이터베이스대로 설계
ORM 프레임워크가 중간에서 매핑
→ ORM은 객체RDB 두 기둥 위에 있는 기술

장점

  • SQL 중심적인 개발에서 객체 중심으로 개발
  • 생산성

    저장: jpa.persist(member)
    조회: Member member = jpa.find(memberId)
    수정: member.setName("변경할 이름")
    삭제: jpa.remove(member)

  • 유지보수

    기존 -> 필드 변경시 모든 SQL 수정
    JPA: 필드만 추가하면 됨, SQL은 JPA가 처리

  • 패러다임의 불일치 해결

    상속
    연관관계, 객체 그래프 탐색 → 신뢰할 수 있는 엔티티, 계층
    비교 → 동일 트랜잭션에서 조회한 엔티티는 같음을 보장

  • 성능
    1. 1차 캐시와 동일성(identity) 보장
      : 같은 트랜잭션 안에서는 같은 엔티티를 반환 - 약간의 조회 성능 향상
    2. 트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)
      ex) 트랜잭션을 커밋할 때까지 INSERT SQL을 모았다가,
      JDBC BATCH SQL 기능을 사용해서 한번에 SQL 전송
    3. 지연 로딩 (Lazy Loading)
      • 지연 로딩: 객체가 실제 사용될 때 로딩
      • 즉시 로딩: JOIN SQL로 한번에 연관된 객체까지 미리 조회
  • 데이터 접근 추상화와 벤더 독립성
  • 표준

프로젝트 생성

IDE: IntelliJ
Build tool: Maven
DB: H2
Java Version: 11

Maven 소스 다운로드

프로젝트를 생성한 후, Maven 소스를 다운로드해줍니다.

persistence.xml

		<persistence-unit name="hello"> <!-- DB -->
        <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.hbm2ddl.auto" value="create" />-->
        </properties>
    </persistence-unit>
  • javax.… : 다른 JPA 구현 라이브러리(MySQL, Oracle과 같은 특정 DB)를 써도 적용되는 것
  • hibernate.… : 다른 JPA 구현 라이브러리를 적용하게 되면 value를 바꿔야 함
  • '필수 속성'의 값들을 자신의 H2 DB(localhost:8082) 설정과 똑같이 맞춰야 합니다!

애플리케이션 개발

Main 파일

프로젝트 폴더 - src - main - java - hellopjpa - JpaMain.class

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

public class JpaMain {

    public static void main(String[] args) {
        EntityManagerFactory enf = Persistence.createEntityManagerFactory("hello");
        // pom.xml의 name 값이 매개변수로 들어가, 설정 파일을 읽어와 만들어진다.

        EntityManager entityManager = enf.createEntityManager();
        //code 
        entityManager.close();

        enf.close();
    }
}

  • EntityManagerFactory는 pom.xml의 name 값이 매개변수로 들어가, 설정 파일을 읽어와 만들어진다. (pom.xml의 <persistence-unit name="hello">의 name 값이 Persistence.createEntityManagerFactory()의 매개변수로 들어간다.)
  • EntityManagerFactory는 애플리케이션이 로딩될 때 한 번만 만들어 전체에서 공유한다.
  • EntityManagerTransaction 단위마다 만들어야 한다.
  • JPA의 모든 데이터 변경 작업을 Transaction 안에서 해야 한다.
  • EntityManagerThread 간에 공유하면 절대로 안된다. (사용하고 버려야 한다.)

회원 테이블

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
// @Table(name = "USER") // Member 테이블이 아닌 USER 테이블에 저장
public class Member {

    @Id
    private Long id;
    // @Column(name = "username") // username 속성에 저장
    private String name;

		public Member() {} // 기본 생성자

		// 가급적이면 setter의 사용을 최소한하고, 생성자에서 값을 세팅하는 것이 좋다. (유지보수성)
    public Member(Long id, String name) {
        this.id = id;
        this.name = name;
    }

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

JPA의 @Entity에는 기본 생성자가 필수로 있어야 한다!

내부적으로 Reflection을 통해 객체를 생성하기 때문이다.
Java Reflection은 구체적인 클래스타입을 알지 못할 때, 해당 클래스의 메소드, 멤버 변수 등을 알게 해주는 기능이다. 하지만, 생성자의 인자 정보들을 알 수 없기 때문에, 기본 생성자가 없다면 Java Reflection 객체를 생성할 수 없다. 따라서, @Entity는 반드시 파라미터가 없는 생성자가 public 혹은 protected로 있어야 한다.

(하이버네이트같은 구현체들은 라이브러리 등을 통해 이런 문제를 회피할 수 있어 당장의 코드가 실행될 수는 있겠지만, JPA 스펙에서 기본 생성자를 필수로 강제했기 때문에 이런 부분은 따르는 것이 좋다고 한다.)

Transaction 정석 코드 (회원 생성, 조회, 수정)

public class JpaMain {

    public static void main(String[] args) {
        EntityManagerFactory enf = Persistence.createEntityManagerFactory("hello");
        // pom.xml의 name 값이 매개변수로 들어감

        EntityManager em = enf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {
        	// ** code
            
        	// 생성
            Member member = new Member();
            member.setId(1L);
            member.setName("HelloA");
            
            Member member = new Member();
            member.setId(2L);
            member.setName("HelloB");

			// 조회 & 출력
        	Member findMember = em.find(Member.class, 2L);
            System.out.println("findMember.id = " + findMember.getId());
            System.out.println("findMember.name = " + findMember.getName());

            // 수정 & 출력
            findMember.setName("HelloJPA");
            System.out.println("findMember.id = " + findMember.getId());
            System.out.println("findMember.name = " + findMember.getName());
            
            // **
            
            // 영속화
            em.persist(member);

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

        enf.close();
    }
}
  • commit()을 통해 반영, 문제가 생기면 rollback()
  • EntityManager가 데이터베이스 커넥션과 같이 동작하기 때문에 close()해주는 것이 중요하다.
  • EntityManager를 Java의 Collection처럼 이해하면 됨.

H2 DB 확인 (http://localhost:8082)

  • JPA를 통해서 Entity를 가져오면, JPA가 해당 Entity를 관리한다.
  • JPA가 commit하는 시점에, 값이 변경되었는지 확인한다.
    → 값이 변경되었으면 업데이트 쿼리를 만들어 날린다.

JPQL

  • 검색 쿼리를 날려야할 때 유용하다
  • 모든 DB 데이터를 객체로 변환해서 검색하는 것은 불가능하다
  • ⇒ 필요한 데이터만 DB에서 불러오려면, 결국 검색 조건이 포함된 SQL이 필요
  • JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어를 제공한다.
  • SQL은 데이터베이스 테이블을 대상으로 쿼리, JPQL은 엔티티 객체를 대상으로 쿼리.
			// code
			List<Member> result = em.createQuery("select m from Member as m", Member.class)
            						.setFirstResult(5)
                    				.setMaxResults(8)
                    				.getResultList();
            
            for (Member member : result) {
            	System.out.println("member = " + member.getName());
           	}
  • .setFirstResult(5).setMaxResults(8)를 통해 페이징 가능

DB에 따른 쿼리 변경 확인

(persistence.xmlhibernate.dialect의 값을 수정)

H2 DB (value="org.hibernate.dialect.H2Dialect")

Oracle DB (value="org.hibernate.dialect.Oracle12cDialect")

→ 쿼리가 바뀐 것을 알 수 있다
⇒ 즉, JPQL은 객체 지향 SQL이며, 특정 데이터베이스 SQL에 의존 하지 않는다.

0개의 댓글