02강. JPA 시작


1. Hello JPA - 프로젝트 생성


H2 데이터베이스 설치와 실행


  • http://www.h2database.com/
  • 장점
    • 웹용 쿼리툴 제공
    • 가벼움 (1.5M)
    • MySQL, Oracle 데이터베이스 시뮬레이션 기능
    • 시퀀스, AUTO INCREMENT 기능 지원



설치 방법(mac)


🚨 주의

  • SpringBoot와 호환되는 버전인 1.4.199 버전 다운로드 받으려면 링크에서 해당 버전 다운받기
  • 다운로드 후 bin의 sh 파일 실행
  • sh 파일 실행 방법 : 폴더로 이동해서 sh (파일명)
  • 화면에 설정 정보 입력
    • 서버 모드 / 사용자명 sa / 비밀번호 입력 X -> 연결



🔎 발생한 에러

  1. Database “dir name” not found, either pre-create it or allow remote database creation (not recommended in secure environments) [90149-214]
  • 해결방법 : DB를 미리 만들어줌 (참고)

1) JDBC URL에 ‘jdbc:h2:~/test’ 입력 후 연결 (연결 시험 XXX)
2) 다시 돌아와서 ‘jdbc:h2:tcp://localhost/~/test’로 연결


  1. File corrupted while reading record: null. Possible solution: use the recovery tool [90030-199] 90030/90030
  • 원인 : H2 버전 바꾸고 이전과 동일한 DB에 연결 시도
  • 해결방법 : 로컬에서 DB 삭제하고 다시 만듦 (삭제해도 상관 없는 DB 였음)


💡 H2 버전 확인 명령어

  • SELECT H2VERSION() FROM DUAL;



Maven


  • https://maven.apache.org/
  • 자바 라이브러리, 빌드 관리
  • 라이브러리 자동 다운로드 및 의존성 관리
  • 최근에는 그래들(Gradle) 사용이 증가



프로젝트 생성


  • 자바 8 이상(8 권장)
  • 메이븐 설정
    • groupId: jpa-basic
    • artifactId: ex1-hello-jpa
    • version: 1.0.0


💡 IntelliJ로 Maven 프로젝트 만들기 (강의와 UI가 다름!) (참고)

  • New Project > Build system : Maven / Advanced Settings
  • Maven Archetype으로 생성 X



라이브러리 추가 - pom.xml


  • pom.xml에 사용할 라이브러리를 dependency로 추가
  • pom.xml에 추가하는 라이브러리

      <dependencies>
      	<!-- JPA 하이버네이트 -->
        <dependency>
      		<groupId>org.hibernate</groupId>
          <artifactId>hibernate-entitymanager</artifactId>
          <version>5.3.10.Final</version>
        </dependency>
        <!-- H2 데이터베이스 -->
        <dependency>
          <groupId>com.h2database</groupId>
          <artifactId>h2</artifactId>
          <version>1.4.199</version>
        </dependency>
      </dependencies>
    
  • hibernate
    • SpringBoot와 호환 위해 5.3.10.Final 버전 사용
    • hibernate-entitymanager : 필요한 의존성을 땡겨옴 (hibernate core, javax.persistence:javax.persistence-api:2.2 등)
      • javax.persistence : JPA 인터페이스를 가지고 있음
  • h2database : DB에 접근하기 위한 드라이버
    • 다운로드 받은 h2 데이터베이스와 같은 버전 사용할 것



JPA 설정하기 - persistence.xml


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
 xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
 <persistence-unit name="hello">
 <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>
</persistence>
  • JPA 설정 파일
  • 위치는 반드시 /META-INF/persistence.xml
  • 설정 사항
    • persistence version="2.2" : jpa 2.2 버전을 사용함
    • 필수 속성 : 데이터베이스 접근 정보 등
    • ⭐️ hibernate.dialect : 데이터베이스 방언
  • persistence-unit name으로 이름 지정 (<persistence-unit name="hello">)
    • 데이터베이스당 하나 만듦


💡 javax.persistence vs. hibernate

  • javax.persistence : JPA 표준 속성 -> hibernate 외의 JPA 구현 라이브러리 사용 가능
  • hibernate : hibernate 전용 속성 -> 다른 라이브러리 사용하면 변경 필요
    • hibernate.show_sql : DB에 나가는 쿼리를 볼 것인지
    • hibernate.format_sql : 쿼리를 포맷팅해서 보여줌
    • hibernate.use_sql_comments : 쿼리에 대한 주석 제공



데이터베이스 방언 (Dialect)


  • 방언 : SQL 표준을 지키지 않는 특정 데이터베이스만의 고유한 기능
  • JPA는 특정 데이터베이스에 종속 X -> 다른 데이터베이스로 변경 용이
  • 각각의 데이터베이스가 제공하는 SQL 문법과 함수는 조금씩 차이가 있음

    ex. 가변 문자: MySQL은 VARCHAR, Oracle은 VARCHAR2

    문자열을 자르는 함수: SQL 표준은 SUBSTRING(), Oracle은 SUBSTR()

    페이징: MySQL은 LIMIT , Oracle은 ROWNUM

  • hibernate.dialect 속성에 value로 지정
    • H2 : org.hibernate.dialect.H2Dialect
    • Oracle 10g : org.hibernate.dialect.Oracle10gDialect
    • MySQL : org.hibernate.dialect.MySQL5InnoDBDialect
  • 하이버네이트는 40가지 이상의 데이터베이스 방언 지원 (현업에서 쓸 수 있는 건 거의 다!)



2. Hello JPA - 애플리케이션 개발


JPA 구동 방식


  • persistence.class에서 시작
  • META-INF/persistence.xml의 설정 정보를 읽어서 EntityManagerFactory 클래스를 만듦
  • EntityManagerFactory에서 필요할 때마다 EntityManager를 생성



실습


  • JpaMain 클래스 생성 및 JPA 동작 확인
package hellojpa;

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

public class JpaMain {

    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");

        EntityManager em = emf.createEntityManager();

        // 여기에 code 작성

        em.close();

        emf.close();
    }
}


🚨 주의

  • jdb url 같은거 쓰는지 확인!! (persistence.xml)
  • Persistence 불러오지 못하면 persistence.xml 확인 필요
  • EntityManagerFactory는 로딩 시점에 하나만 생성 (DB 당 하나)
  • 실제 DB에 저장하는 트랜잭션 단위마다 entity manager 꼭 만들어줘야 함



객체와 테이블을 생성하고 매핑하기


1) DB에 Member table 생성

create table Member (
	id bigint not null,
	name varchar(255),
	primary key (id)
);


2) 테이블과 매핑되는 Member class 생성

  • @Entity : JPA가 관리할 객체
  • @Id : 데이터베이스 PK와 매핑
package hellojpa;

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity // JPA가 관리할 객체임을 JPA가 인식할 수 있도록
public class Member {

    @Id // PK
    private long id;
    private String name;

    // getter, setter
    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;
    }
}



CRUD 실습


  • 회원 등록

      public class JpaMain {
        
          public static void main(String[] args) {
      				// 1. EntityManageFactory 생성
              EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        
      				// 2. EntityManagerFactory 이용하여 EntityManager 생성
              EntityManager em = emf.createEntityManager();
        
              // 3. Transaction 생성하여 begin
              EntityTransaction tx = em.getTransaction();
              tx.begin();
        
      				// 4. 실행할 코드
              // 멤버를 저장해보자
              Member member = new Member();
        
              member.setId(1L);
              member.setName("HelloA");
              em.persist(member);
        
      				// 5. 트랜잭션 commit
              tx.commit();
        
      				// 6. 트랜잭션 끝나면 EntityManager close
              em.close();
        
      				// 7. 모두 종료되면 EntityManagerFactory도 close
              emf.close();
          }
      }
    
    • 실행 결과 -> JPA가 매핑 정보를 보고 추가해줌

        Hibernate: 
            /* insert hellojpa.Member
                */ insert 
                into
                    Member
                    (name, id) 
                values
                    (?, ?)
      


💡 정석 코드

  • commit 후(try) 실패하면 rollback(catch). 트랜잭션 종료되면 EntityManager close(finally)
  • SpringBoot에서 해줌
public class JpaMain {

    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");

        EntityManager em = emf.createEntityManager();

        // 트랜잭션 단위 꼭 !!!!
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {
            Member member = new Member();

            member.setId(2L);
            member.setName("HelloB");
            em.persist(member);

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

        emf.close();
    }
}
  • 회원 단 건 조회

      public class JpaMain {
        
          public static void main(String[] args) {
              EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        
              EntityManager em = emf.createEntityManager();
        
              // 트랜잭션 단위 꼭 !!!!
              EntityTransaction tx = em.getTransaction();
              tx.begin();
        
              try {
                  Member findMember = em.find(Member.class, 1L);
        
                  System.out.println("findMember.id = " + findMember.getId());
                  System.out.println("findMember.name = " + findMember.getName());
        
                  tx.commit();
              } catch (Exception e) {
                  tx.rollback();
              } finally {
                  em.close();
              }
        
              emf.close();
          }
      }
    
  • 회원 수정 : 수정할 항목을 찾고, setter로 변경

      public class JpaMain {
        
          public static void main(String[] args) {
              EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        
              EntityManager em = emf.createEntityManager();
        
              // 트랜잭션 단위 꼭 !!!!
              EntityTransaction tx = em.getTransaction();
              tx.begin();
        
              try {
                  // 수정할 멤버를 찾음
                  Member findMember = em.find(Member.class, 1L);
        
                  // setter로 바꿔줌 (persistence 안해도 저장됨!)
                  findMember.setName("HelloJPA");
        
                  tx.commit();
              } catch (Exception e) {
                  tx.rollback();
              } finally {
                  em.close();
              }
        
              emf.close();
          }
      }
    
  • 회원 삭제 : 삭제할 항목 찾고 remove

      public class JpaMain {
        
          public static void main(String[] args) {
              EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        
              EntityManager em = emf.createEntityManager();
        
              // 트랜잭션 단위 꼭 !!!!
              EntityTransaction tx = em.getTransaction();
              tx.begin();
        
              try {
                  // 삭제할 멤버를 찾음
                  Member findMember = em.find(Member.class, 1L);
        
                  // 그리고 remove로 삭제
                  em.remove(findMember);
                    
                  tx.commit();
              } catch (Exception e) {
                  tx.rollback();
              } finally {
                  em.close();
              }
        
              emf.close();
          }
      }
    



💡 DB의 테이블명 <> 객체 이름인 경우? DB 컬럼명 <> 객체 속성명인 경우? : Annotation에 추가

@Entity
@Table(name = "USER") // DB의 Member 테이블이 아닌 USER 테이블에 추가
public class Member {

    @Id // PK
    private long id;

		@Column(name = "username") // 만약 DB column은 name이 아니고 username이면
    private String name;

    // getter, setter
    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;
    }
}



🚨 주의


  • EntityManagerFactory : 하나만 생성해서 애플리케이션 전체에서 공유
  • EntityManager : 쓰레드간 공유 X
  • JPA의 모든 데이터 변경은 트랜잭션 안에서 실행



3. JPQL


JPQL


  • JPA를 사용하면 엔티티 객체를 중심으로 개발
    • 문제점 : 검색쿼리 (단 건 이상. 최대한 필터링 필요)
      • 검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 검색
      • 모든 DB 데이터를 객체로 변환해서 검색하는 것은 불가능

      → 필요한 데이터만 불러오려면 결국 검색 조건이 포함된 SQL이 필요

  • JPQL : SQL을 추상화한 객체 지향 쿼리 언어로 JPA가 제공
    • SQL과 문법 유사 : SELECT, FROM, WHERE, GROUP BY, HAVING, JOIN 지원
    • SQL은 데이터베이스 테이블을 대상으로 쿼리 → 방언을 바꾸어도 JPQL 쿼리를 바꿀 필요 X
    • 테이블이 아닌 객체를 대상으로 검색하는 객체 지향 쿼리
    • SQL을 추상화 → 특정 데이터베이스 SQL에 의존X



JPQL 예제


  • 예제 1. JPQL로 전체 회원 검색

      public class JpaMain {
        
          public static void main(String[] args) {
              EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        
              EntityManager em = emf.createEntityManager();
        
              EntityTransaction tx = em.getTransaction();
              tx.begin();
        
              try {
        
                  List<Member> result = em.createQuery("select m from Member as m", Member.class) // Member 객체를 대상으로 쿼리 (쿼리의 대상이 테이블이 아님)
                          .getResultList();
        
                  for (Member member : result) {
                      System.out.println("member.name = " + member.getName());
                  }
        
                  tx.commit();
              } catch (Exception e) {
                  tx.rollback();
              } finally {
                  em.close();
              }
        
              emf.close();
          }
      }
    
    • 결과 : JPQL은 m이라는 Member 엔티티로 조회

        Hibernate: 
            /* select
                m 
            from
                Member as m */ select
                    member0_.id as id1_0_,
                    member0_.name as name2_0_ 
                from
                    Member member0_
        member.name = HelloJPA
        member.name = HelloB
      
  • 예제 2.

      public class JpaMain {
        
          public static void main(String[] args) {
              EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        
              EntityManager em = emf.createEntityManager();
        
              // 트랜잭션 단위 꼭 !!!!
              EntityTransaction tx = em.getTransaction();
              tx.begin();
        
              try {
        
                  List<Member> result = em.createQuery("select m from Member as m", Member.class)
                          .setFirstResult(1)
                          .setMaxResults(8)
                          .getResultList();
        
                  for (Member member : result) {
                      System.out.println("member.name = " + member.getName());
                  }
        
                  tx.commit();
              } catch (Exception e) {
                  tx.rollback();
              } finally {
                  em.close();
              }
        
              emf.close();
          }
      }
    
    • 결과

        Hibernate: 
            /* select
                m 
            from
                Member as m */ select
                    member0_.id as id1_0_,
                    member0_.name as name2_0_ 
                from
                    Member member0_ limit ? offset ?
        member.name = HelloB
      
      • limit, offset 자동으로 쿼리
      • oracle로 방언 바꾸면 oracle로 쿼리
  • JPQL에 대해 자세한 내용은 이후에 객체지향 쿼리에서 학습

Leave a comment