JPA 시작하기
개요
사용 라이브러리
- hibernate-entitymanager
- h2
h2 DB를 설치하여, 학습용 DB로 사용한다. 또한, JPA를 사용하기 위해 hibernate 라이브러리를 사용한다.
객체 매핑
-
기초적인 자바 프로젝트를 maven과 함께 생성하여, 객체 매핑에 대해 알아본다.
참고로, spring이나 spring boot를 사용하지 않는다. 단순히 main 메서드로 동작하는 프로젝트이다.
-
예시 프로젝트의 링크는 상단에 작성해두었으니 참고하시기 바란다.
회원 테이블 생성
- H2 콘솔을 통해 아래 SQL을 입력하여, MEMBER 테이블을 생성한다.
CREATE TABLE MEMBER (
ID VARCHAR(255) NOT NULL,
NAME VARCHAR(255),
AGE INTEGER,
PRIMARY KEY (ID)
)
회원 클래스
- JPA 애플리케이션에서 사용될 회원 클래스를 작성한다.
- 해당 클래스는 MEMBER 테이블과 매핑될 클래스이다.
public class Member {
private String id;
private String username;
private Integer age;
// Getter, Setter 생략
}
매핑 애너테이션 설정
- JPA를 사용하려면 가장 먼저 회원 클래스와 회원 테이블을 매핑해야 한다.
- 애너테이션을 통해 매핑할 수 있다.
@Entity
@Table(name="MEMBER")
public class Member {
@Id
@Column(name = "ID")
private String id;
@Column(name = "NAME")
private String username;
//매핑정보가 없다.
private Integer age;
//Getter, Setter 생략
}
- 사용된 애너테이션은 다음과 같다.
@Entity
@Table
@Id
@Column
- 애너테이션 설명은 아래에서 계속한다.
매핑 애너테이션 설명
@Entity
- 클래스 레벨에 적용한다.
- 해당 클래스를 테이블과 매핑한다고 JPA에게 알려주는 역할을 한다.
- 해당 애너테이션이 적용된 클래스를 엔티티 클래스라고 한다.
@Table
- 클래스 레벨에 적용한다.
- 엔티티 클래스에 매핑할 테이블 정보를 JPA에게 알려주는 역할을 한다.
- 위 예시 코드에서는
name
속성으로 테이블의 이름을 지정하여, ‘MEMBER 테이블’과 ‘Member 엔티티’를 매핑하였다.@Table(name = "테이블이름")
- 해당 애너테이션을 생략하면 클래스 이름을 테이블 이름으로 매핑한다.
@Id
- 필드 레벨에 적용한다.
- 엔티티 클래스의 필드를 테이블의 기본 키 (Primary Key)에 매핑한다.
- 해당 애너테이션이 적용된 필드를 식별자 필드라고 한다.
@Column
- 필드 레벨에 적용한다.
- 해당 필드를 컬럼에 매핑한다.
- 위 예시 코드에서는
name
속성으로 매핑할 컬럼의 이름을 지정하여, ‘MEMBER 테이블의 NAME 컬럼’과 ‘name 필드’를 매핑하였다.@Column(name = "매핑할 컬럼명")
- 매핑 정보가 없는 필드
- 매핑 애너테이션을 생략하면 필드명을 사용해서 컬럼명으로 매핑한다.
- 이름이 age인 필드에 매핑 정보를 생략하면, age컬럼으로 자동 매핑한다.
- 만약, 대소문자를 구분하는 DB를 사용하면,
@Column
을 사용하여 명시적으로 매핑해야 한다.
javax.persistence
: JPA 애너테이션의 패키지
persistence.xml
설정
persistence.xml
이란?
- JPA는
persistence.xml
를 통해 필요한 설정 정보를 관리한다. - 해당 xml 파일은
/src/main/resource/META-INF
경로에 위치한다. - 해당 경로에 위치했을 때, 별도의 설정없이 해당
persistence.xml
파일을 JPA가 인식할 수 있다.
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 xmlns="http://xmlns.jcp.org/xml/ns/persistence" version="2.1">
- 설정 파일은
persistence
태그로 시작한다.
- 설정 파일은
<persistence-unit name="jpabook">
- JPA 설정은 영속성 유닛(persistence-unit) 이라는 것부터 시작하게 된다.
- 해당 태그를 통해, 연결할 데이터베이스당 하나의 영속성 유닛을 등록한다.
- 영속성 유닛에는 고유한 이름으로 부여해야 한다.
- 위 코드에선 ‘jpabook’이라는 이름을 부여했다.
- 속성 분석
- JPA 표준 속성
javax.persistence.jdbc.driver
: JDBC 드라이버javax.persistence.jdbc.user
: DB 접속 아이디javax.persistence.jdbc.password
: DB 접속 비밀번호javax.persistence.jdbc.url
: DB 접속 URL
- 하이버네이트 속성
hibernate.dialect
: DB 방언 설정hibernate.show_sql
: 하이버네이트가 실행한 SQL을 출력한다.hibernate.format_sql
: 하이버네이트가 실행한 SQL을 출력할 때 보기 쉽게 정렬한다.hibernate.use_sql_comments
: 쿼리를 출력할 때 주석도 함께 출력한다.-
hibernate.id.new_generator_mappings
: JPA 표준에 맞춘 새로운 키 생성 전략을 사용한다.해당 사항은 나중에 자세히 다룬다.
- 설명
- 이름이
javax.persistence
로 시작하는 속성은 JPA 표준 속성이다. 따라서 특정 구현체 (Hibernate 등)에 종속되지 않는다. hibernate
로 시작하는 속성은 하이버네이트 전용 속성이므로 하이버네이트에서만 사용할 수 있다.
DB 방언에 대해서는 아래에서 자세히 다룬다.
- 이름이
- JPA 표준 속성
데이터베이스 방언
- JPA는 특정 DB에 종속적이지 않은 기술이다.
- 따라서, 다른 DB로 손쉽게 교체할 수 있다.
- 하지만 각 DB마다 SQL 문법과 함수가 조금씩 다르다는 문제가 있다.
- SQL 표준을 지키지 않거나 특정 DB만의 고유한 기능을 JPA에서는 방언이라고 한다.
- 이러한 문제를 해결하고자, JPA 구현체(Hibernate 등)에서는 다양한 DB 방언 클래스를 제공한다.
- 특정 DB에 의존적인 SQL을 DB 방언이 처리해준다.
- 따라서, DB가 변경되어도 애플리케이션 코드를 변경할 필요 없이, DB 방언만 교체하면 된다.
애플리케이션 개발
JPA 애플리케이션 실행 클래스 (main)
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) {
e.printStackTrace();
tx.rollback(); //트랜잭션 롤백
} finally {
em.close(); //엔티티 매니저 종료
}
emf.close(); //엔티티 매니저 팩토리 종료
}
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);
}
}
- 위 코드는 크게 아래처럼 구분할 수 있다.
- 엔티티 매니저 설정
- 트랜잭션 관리
- 비즈니스 로직
- 각 항목에 대해 하나씩 알아보자.
엔티티 매니저 설정
- 엔티티 매니저 팩토리 생성
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");
- 위 코드를 작성시,
META-INF/persistence.xml
파일에서 이름이 jpabook인 영속성 유닛을 찾아서 엔티티 매니저 팩토리를 생성한다.- 즉 매개변수로 전달받은, 해당 이름을 갖는 영속성 유닛을
persistence.xml
에서 찾아내어, 해당 정보로 엔티티 매니저 팩토리를 생성한다.
- 즉 매개변수로 전달받은, 해당 이름을 갖는 영속성 유닛을
- 엔티티 매니저 팩토리는 애플리케이션 전체에서 딱 한 번만 생성하고 공유해서 사용해야 한다.
- 왜냐하면, 엔티티 매니저 팩토리를 만드는 과정에서 소요되는 비용이 매우 크다.
- 소요 비용
persistence.xml
설정 정보 읽은 뒤, JPA를 동작시키기 위한 기반 객체 생성- JPA 구현체에 따라서는 DB 커넥션 풀까지 생성
- 위 코드를 작성시,
- JPA 시작 전에 먼저,
**persistence.xml
의 설정 정보를 사용하여 엔티티 매니저 팩토리를 생성해야 한다.** - 이때
Persistence
클래스가 사용된다.- 해당 클래스는 엔티티 매니저 팩토리를 생성해서 JPA를 사용할 수 있게 준비한다.
- 엔티티 매니저 생성
EntityManager em = emf.createEntityManager();
- 엔티티 매니저 팩토리에서 엔티티 매니저를 생성한다.
- 엔티티 매니저
- JPA의 기능 대부분은 엔티티 매니저가 제공한다.
- 대표적인 기능으로, 엔티티 매니저를 통한 엔티티 CRUD 작업이 존재한다.
- 엔티티 매니저는 내부에 데이터소스(DB 커넥션)을 유지하면서 DB와 통신한다.
- 따라서, 개발자는 엔티티 매니저를 가상의 DB로 생각할 수 있다.
- 엔티티 매니저는 DB 커넥션과 밀접한 관계가 있으므로, 쓰레드 간의 공유나 재사용을 하면 안된다.
- JPA의 기능 대부분은 엔티티 매니저가 제공한다.
- 종료
em.close();
- 마지막으로 사용이 끝난 엔티티 매니저는 위와 같이 반드시 종료해야 한다.
emf.close();
- 애플리케이션을 종료할 때, 엔티티 매니저 팩토리도 위와 같이 반드시 종료해야 한다.
트랜잭션 관리
- JPA를 사용하면, 항상 트랜잭션 안에서 데이터를 변경해야 한다.
- 트랜잭션 없이 데이터를 변경하면 예외가 발생한다.
- 트랜잭션을 시작하려면, 엔티티 매니저에서 트랜잭션 API를 받아와야 한다.
EntityTransaction tx = em.getTransaction(); //트랜잭션 기능 획득
try {
tx.begin(); //트랜잭션 시작
logic(em); //비즈니스 로직
tx.commit();//트랜잭션 커밋
} catch (Exception e) {
e.printStackTrace();
tx.rollback(); //트랜잭션 롤백
}
EntityTransaction tx = em.getTransaction();
- 엔티티 매니저로부터 트랜잭션을 받아온다.
tx.commit();
- 비즈니스 로직이 정상 동작하면, 트랜잭션을 커밋한다.
tx.rollback();
- 예외가 발생하면 트랜잭션을 롤백한다.
비즈니스 로직
- 위 예시에서의 비즈니스 로직은 다음과 같다.
- 회원 엔티티를 하나 생성한 다음 엔티티 매니저를 통해 DB에 CRUD 작업을 한다.
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); }
- 등록, 수정, 삭제, 조회 작업이 엔티티 매니저를 통해서 수행된다.
- 등록
em.persist(member);
- 엔티티를 저장하려면, 엔티티 매니저의
persist()
메서드에 저장할 엔티티를 넘겨주면 된다. - JPA가 매핑 정보(애너테이션)를 분석해서 만들어낸 SQL
INSERT INTO MEMBER (ID, NAME, AGE) VALUES ('id1', '지한', 2)
- 수정
member.setAge(20);
- 엔티티를 수정한 후에 수정 내용을 반영하려면, 그저 엔티티의 프로퍼티를 수정하는 것이 전부이다.
- JPA는 어떤 엔티티가 변경되었는지 추적하는 기능을 가지고 있기 때문에, 엔티티의 값만 변경하면 알아서
UPDATE
SQL을 생성하여 DB에 값을 변경한다. - 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);
- ‘조회할 엔티티 타입’과 ‘@ID’을 통해, DB 테이블의 기본 키와 매핑한 식별자 값으로 엔티티 하나를 조회한다.
- JPA가 매핑 정보(애너테이션)를 분석해서 만들어낸 SQL
SELECT * FROM MEMBER WHERE ID='id1'
- 여러 건 조회
- 아래에서 자세히 설명한다.
JPQL
- 하나 이상의 회원 목록을 조회하는 코드를 자세히 살펴보자.
List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();
- 원래 JPA는 엔티티 객체를 중심으로 개발하므로, 검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 검색해야 한다. 이에 대한 과정을 나타내면 아래와 같다.
- DB의 모든 데이터를 APP으로 불러온다.
- 불러온 데이터를 모두 엔티티 객체로 변경한다.
- 검색을 진행한다.
- 하지만 이러한 과정은 사실상 불가능하다.
- 따라서, 애플리케이션이 필요한 데이터만 DB에서 불러오려면 결국 검색 조건이 포함된 SQL을 사용해야 한다.
- JPA는 이러한 문제를 JPQL이라는 쿼리 언어로 해결한다.
- JPQL
- 쿼리 언어의 한 종류이다.
- SQL과 문법이 거의 유사하여 SELECT, FROM, WHERE, GROUP BY, BAVING, JOIN 등을 사용할 수 있다.
- SQL과의 차이점
- JPQL: 엔티티 객체를 대상으로 쿼리한다.
- SQL: DB 테이블을 대상으로 쿼리한다.
- JPQL 예시
select m from Member
- 여기서의
from Member
는 회원 엔티티 객체를 뜻한다. (MEMBER 테이블을 뜻하는 것이 아니다.) - JPQL은 DB테이블을 전혀 알지 못한다.
- JPQL 사용
- JPQL을 사용하려면, 먼저
em.createQuery(JPQL, 반환 타입)
메서드를 실행해서 쿼리 객체를 생성한 후, 쿼리 객체의getResultList()
메서드를 호출하면 된다. - JPA가 매핑 정보(애너테이션)를 분석해서 만들어낸 SQL
SELECT M.ID, M.NAME, M.AGE FROM MEMBER M
- JPQL을 사용하려면, 먼저
- 보다 자세한 내용은 추후에 다루겠다.
- 김영한, 『자바 ORM 표준 JPA 프로그래밍』, 에이콘