JPA 시작

신윤섭·2023년 6월 28일

JPA 정복하기

목록 보기
2/3

이 글은 김영한님께서 지으신 '자바 ORM 표준 JPA 프로그래밍'의 내용을 바탕으로 작성하였습니다.

1. 이클립스 설치와 프로젝트 불러오기

이클립스는 LUNA 버전 이상으로 설치한다.
프로젝트 불러오기는 생략한다.

2. H2 데이터베이스 설치

설치가 필요 없고 용량도 1.7M로 가벼운 H2 데이터베이스를 사용한다.
H2 데이터베이스는 JVM 메모리 안에서 실행되는 임베디드 모드와 실제 데이터베이스처럼 별도의 서버를 띄워서 동작하는 서버 모드가 있다.

  • 드라이버 클래스 : org.h2.Driver
  • JDBC URL : jdbc:h2:tcp://localhost/~/test
  • 사용자명 : sa
  • 비밀번호 : 입력하지 않는다.

test라는 이름의 데이터베이스에 서버 모드로 접근할 수 있다.

3. 라이브러리와 프로젝트 구조

빌드 도구는 메이븐을 사용한다. 메이븐은 라이브러리 관리 기능과 빌드 기능을 제공한다.
pom.xml 파일에 dependencies 태그 안에 사용할 라이브러리를 지정한다.

...
<dependencies>
  <!-- JPA, 하이버네이트 -->
  <dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-entitymanager</artifactId>
    <version>4.3.10.Final</version>
  </dependency>
  <!-- H2 데이터베이스 -->
  <dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>1.4.187</version>
  </dependency>
</dependencies>
...

4. 객체 매핑 시작

회원 테이블 생성

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

회원 클래스 생성

public class Member {
	private String id;
    private String username;
    private Integer age;
    
    //Getter, Setter
    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;
    }
}

JPA를 사용하려면 가장 먼저 회원 클래스와 회원 테이블을 매핑해야 한다.

매핑 정보회원 객체회원 테이블
클래스와 테이블MemberMEMBER
기본 키idID
필드와 컬럼usernameNAME
필드와 컬럼ageAGE

매핑 어노테이션 추가

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

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

@Table

  • 엔티티 클래스에 매핑할 테이블 정보를 알려준다.
  • name 속성에는 매핑할 테이블의 이름이 들어간다.(Member -> MEMBER 매핑)
  • 생략하면 클래스 이름을 테이블 이름으로 매핑한다.(정확히는 엔티티 이름을 사용한다.)

@Id

  • 엔티티 클래스의 필드를 테이블의 기본 키(Primary Key)에 매핑한다.
  • @Id가 사용된 필드를 식별자 필드라 한다.

@Column

  • 필드를 컬럼에 매핑한다.
  • name 속성에는 매핑할 컬럼의 이름이 들어간다.(username -> NAME 매핑)

매핑 정보가 없는 필드

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

매핑 정보 덕분에 JPA는 어떤 엔티티를 어떤 테이블에 저장해야 하는지 알 수 있다.

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" >
    <properties>
      
      <!-- 필수 속성 -->
      <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/> <!-- JDBC 드라이버 -->
      <property name="javax.persistence.jdbc.user" value="sa"/> <!-- 데이터베이스 접속 아이디 -->
      <property name="javax.persistence.jdbc.pasword" value=""/> <!-- 데이터베이스 접속 비밀번호 -->
      <property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test"/> <!-- 데이터베이스 접속 URL -->
      <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/> <!-- 데이터베이스 방언 설정 -->
      
      <!-- 옵션 -->
      <property name="hibernate.show_sql" value="true" /> <!-- 하이버네이트가 실행한 SQL을 출력한다. -->
      <property name="hibernate.format_sql" value="true" /> <!-- 하이버네이트가 실행한 SQL을 출력할 때 보기 쉽게 정렬한다. -->
      <property name="hibernate.user_sql_comments" value="true" /> <!-- 쿼리를 출력할 때 주석도 함께 출력한다. -->
      <property name="hibernate.id.new_generator_mappings" value="true" /> <!-- JPA 표준에 맞춘 새로우 키 생성 전략을 사용한다. -->
      
    </properties>
  </persistence-unit>
</persistence>

데이터베이스 방언

SQL 표준을 지키지 않거나 특정 데이터베이스만의 고유한 기능을 JPA에서는 방언(Dialect)이라 한다. 개발자는 JPA가 제공하는 표준 문법에 맞추어 JPA를 사용하면 되고, 특정 데이터베이스에 의존적인 SQL은 데이터베이스 방언이 처리해준다. 따라서 데이터베이스가 변경되어도 애플리케이션 코드를 변경할 필요 없이 데이터베이스 방언만 교체하면 된다.

하이버네이트는 다양한 데이터베이스 방언을 제공한다.

  • H2 : org.hibernate.dialect.H2Dialect
  • 오라클 10g : org.hibernate.dialect.Oracle10gDialcet
  • MySQL : org.hibernate.dialect.MySQL5InnoDBDialect

6. 애플리케이션 개발

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

public class JpaMain {
	
    public static void main(String[] args) {
    	
        //[엔티티 매니저 팩토리] - 생성
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");
        //[엔티티 매니저] - 생성
        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 logic(EntityManager em) {...}
}

1) 엔티티 매니저 설정

엔티티 매니저 팩토리 생성

EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");
  • META-INF/persistence.xml에서 이름이 jpabook인 영속성 유닛을 찾아서 엔티티 매니저 팩토리를 생성한다.
  • 엔티티 매니저 팩토리를 생성하는 비용은 매우 크기 때문에 애플리케이션 전체에서 딱 한 번만 생성하고 공유해서 사용해야 한다.

엔티티 매니저 생성

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

종료

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

2) 트랜잭션 관리

EntityTransaction tx = em.getTransaction();
try {
        
	tx.begin();		//[트랜잭션] - 시작
    logic(em);		//비즈니스 로직 실행
    tx.commit();	//[트랜잭션] - 커밋
            
} catch (Exception e) {
    tx.rollback();	//[트랜잭션] - 롤백
}
  • JPA를 사용하면 항상 트랜잭션 안에서 데이터를 변경해야 하며 트랜잭션 없이 데이터를 변경하면 예외가 발생한다.
  • 트랜잭션을 시작하려면 엔티티 매니저에서 트랜잭션 API를 받아와야 한다.
  • 트랜잭션 API를 사용해서 비즈니스 로직이 정상 동작하면 트랜잭션을 커밋하고 예외가 발생하면 트랜잭션을 롤백한다.

3) 비즈니스 로직

public 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());
    
    //목록 조회
    List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();
    System.out.println("members.size=" + members.size());
    
    //삭제
    em.remove(member);
}

//출력 결과
findMember=지한, age=20
members.size=1

비즈니스 로직을 보면 등록, 수정, 삭제, 조회 작업이 엔티티 매니저를 통해서 수행되는 것을 알 수 있다. 엔티티 매니저는 객체를 저장하는 가상의 데이터베이스처럼 보인다.

등록

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

//등록
em.persist(member);
  • 엔티티 매니저의 persistence() 메소드에 저장할 엔티티를 넘겨 엔티티를 저장한다.
  • JPA는 매핑 정보를 분석해서 다음과 같은 SQL을 만들어 데이터베이스에 전달한다.
    INSERT INTO MEMBER (ID, NAME, AGE) VALUES ('id1', '지한', 2)

수정

//수정
member.setAge(20);
  • JPA는 어떤 엔티티가 변경되었는지 추적하는 기능을 갖추고 있다.
  • 엔티티의 값만 변경하면 다음과 같은 SQL을 생성해서 데이터베이스에 값을 변경한다.
    UPDATE MEMBER SET AGE=20, NAME='지한', WHERE ID='id1'

삭제

em.remove(member);
  • 엔티티 매니저의 remove() 메소드에 삭제할 엔티티를 넘겨 엔티티를 삭제한다.
  • JPA는 다음과 같은 SQL을 만들어 데이터베이스에 전달한다.
    DELETE FROM MEMBER WHERE ID = 'id1'

한 건 조회

Member findMember = em.find(Member.class, id);
  • find() 메소드는 조회할 엔티티 타입과 @Id로 데이터베이스 테이블의 기본 키와 매핑한 식별자 값으로 엔티티 하나를 조회하는 메소드다.
  • 메소드를 호출하면 다음 SQL을 생성해서 데이터베이스에 결과를 조회하고 결과 값으로 엔티티를 생성해서 반환한다.
    SELECT * FROM MEMBER WHERE ID='id1'

JPQL

//목록 조회
TypedQuery<Member> query = em.createQuery("select m from Member m", Member.class);
List<Member> members = query.getResultList();
  • JPA는 SQL을 추상화한 JPQL이라는 객체지향 쿼리 언어를 제공한다.
  • JPQL과 SQL의 차이점
    • JPQL은 엔티티 객체를 대상으로 쿼리한다. (클래스와 필드)
    • SQL은 데이터베이스 테이블을 대상으로 쿼리한다.
  • 위의 코드에서 from Member는 MEMBER 테이블이 아닌 회원 엔티티 객체를 말한다.
  • JPQL은 데이터베이스 테이블을 전혀 알지 못한다.
  • em.createQuery(JPQL, 반환 타입) 메소드를 실행해서 커리 객체를 생성한 후 쿼리 객체의 getResultList() 메소드를 호출하여 사용한다.
  • JPA는 JPQL을 분석해서 다음과 같은 적절한 SQL을 만들어 데이터베이스에서 데이터를 조회한다.
    SELECT M.ID, M.NAME, M.AGE FROM MEMBER M

0개의 댓글