0. 개요
- JPA를 본격적으로 배우기에 앞서 기본적인 프로젝트 구성과 사용 방법에 대해서 간단히 알아보자.
- 참고로 본 프로젝트는 Maven을 사용한 빌드 구성임을 알려드립니다.
1. JPA 설정 파일
- 기본적인 설정 파일에 대해서 알아보자.
a) pom.xml
- 프로젝트 빌드 설정 파일
- 기본적으로 Spring을 사용하기 때문에, Spring 프로젝트와 관련된 설정을 작성한다.
- JPA를 사용하기 위해서 JPA의 구현체 중 하나를 import 한다.
- 아래의 예시 코드는 Hibernate 라이브러리를 설정하는 코드다.
<!-- JPA 하이버네이트 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.3.10.Final</version>
</dependency>
b) persistence.xml
- JPA 설정 파일
- JPA의 기본적인 옵션을 설정하는 파일이다.
- persistence.xml은 /resources/META-INF/ 디렉터리에 위치하도록 설정한다.
<?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>
<!-- DB 설정 -->
<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/~/jpashop"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
<!-- JPA 옵션 -->
<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>
→ show_sql: commit 시점에서 JPA가 작성한 SQL을 콘솔에 출력한다.
→ format_sql: show_sql 옵션으로 출력되는 SQL을 읽기 쉽도록 formatting 한다.
→ use_sql_comments: JPA에 의해 작성된 SQL이 어떤 명령을 수행하는지, 어떤 객체를 사용하는지 콘솔에 출력한다.
→ hbm2ddl.auto: 스키마 DDL을 검증한다. 설정 값에 따라 다양한 기능을 수행한다.
* DB 방언(hibernate.dialect 옵션)이란?
→ JPA의 CRUD를 사용하면 알아서 연결된 DB의 명령어로 변환해주는 기능
→ 일반적으로 DB가 수행하는 기본적인 기능은 동일하지만, 다른 명령어를 사용한다.
→ 이에 따라 잘못된 DML, DDL이 작성되지 않도록, 연결된 DB가 무엇인지 JPA에게 알려줘야 한다.
→ 이 기능 덕분에 JPA는 특정 DB에 종속되지 않는다.
2. JPA의 기본 구성
- 지금부터 설명할 내용은 나중에 다시 언급할 예정이다.
- 아래의 그림은 JPA가 어떤 과정을 거쳐서 시동되는지 설명한 것이다.
- 현시점에서는 이 흐름을 인지하도록 하자.
a) JPA 동작 방식
- JPA는 기본적으로 하나의 Persistence로부터 시작된다.
- JPA는 Transaction 단위로 DB와 네트워킹을 수행한다.
- Transaction을 기반으로 생성된 SQL을 DB에게 전달하는 역할을 Entity Manager가 수행한다.
- 그래서 하나의 Transaction을 수행할 때마다 하나의 Entity Manager를 필요로 한다.
- 여기서 알 수 있는 것은 Entity Manager가 Transaction과 동일한 라이프사이클을 가진다는 것이다.
- 더 자세한 이야기는 추후에 설명할 예정이다.
b) Persistence Context
- 왠지 Spring의 Application Context(= Spring Container)와 어감이 비슷하지 않은가?
- 이해하기 쉽게 설명하자면 Spring Container처럼 JPA를 관리하는 역할이라고 생각해도 좋을 것 같다.
- 그러나 주의해야 할 점은, Persistence Context는 눈에 보이지 않는 논리적인 개념이라는 것이다.
- Persistence Context는 "객체(Entity)를 영구히 저장할 수 있는 환경"을 정의해놓은 논리적 개념이다.
- 즉, DB에 객체를 저장하는 내부 동작 과정 그 자체를 Persistence Context라고 한다.
c) Entity Manager Factory
→ 웹 서버가 올라오는 시점에서 DB당 딱 1개의 객체만 생성된다.
→ 마치 Spring Container처럼 JPA 객체를 관리한다.
d) Entity Manager
→ 고객의 요청이 올 때마다 생성되어 하나의 트랜잭션과 동일한 라이프사이클을 가지는 객체
→ 마치 Servlet에서 고객의 요청마다 request 객체를 생성하고 응답과 함께 소멸되는 것처럼,
고객의 요청에 의해 생성되고 트랜잭션 종료와 함께 소멸된다.
→ Entity Manager는 절대로 스레드끼리 공유하면 안 된다.
e) Transaction cycle
→ JPA를 이용한 모든 데이터 변경은 하나의 트랜잭션 사이클 안에서 실행된다.
f) 코드로 보는 JPA의 동작 흐름
- 앞서 설명했듯이 JPA는 Transaction 단위로 하나의 사이클을 수행한다.
- 아래의 코드를 보면, 코드 또한 Transaction 단위로 비즈니스를 처리하도록 설계되어 있다.
- 현시점에서는 이 정도까지만 이해하자.
public class JpaMain {
public static void main(String[] args) {
// Entity Manager Factory 생성
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
// Entity Manager 생성
EntityManager em = emf.createEntityManager();
// 트랜잭션 생성
EntityTransaction tx = em.getTransaction();
// 트랜잭션 시작
tx.begin();
try {// 비즈니스 로직 구현부
// commit 실행
tx.commit();
} catch (Exception e) { // 예외 발생시 rollback 수행
tx.rollback();
} finally {
// Transaction이 완료되면 반드시 EntityManager를 종료한다.
em.close();
}
// Transaction이 완료되면 EntityManagerFactory도 종료한다.
emf.close();
}
}
3. Entity
- 간단하게 Entity의 구조와 역할에 대해서 알아보자.
a) Entity란?
- JPA가 관리하는 대상, 객체
- DB 테이블을 Java 클래스로 구성한다.
b) Entity의 구조
- @Entity를 사용하여 해당 클래스가 특정 테이블을 객체화시킨 것이라 명시한다.
- @Id를 사용하여 DB 테이블의 기본 키(Primary key)를 명시한다.
- 필수적으로 Getter, Setter를 생성한다.
@Entity
public class Member {
// JPA가 관리할 객체
// DB의 테이블과 동일하게 구성
@Id
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;
}
}
4. 비즈니스 로직 구현부
- 앞서 설명했듯이, JPA는 Transaction 단위로 하나의 Cycle을 갖는다.
- 그러므로 하나의 Cycle 내부에서 비즈니스 로직을 수행하게 된다.
- 하나의 Transaction 단위로 비즈니스 로직을 수행하므로 트랜잭션의 시작과 끝을 설정한다.
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
// 트랜잭션 생성
EntityTransaction tr = em.getTransaction();
// 트랜잭션 시작
tr.begin();
// 로직 구현부
Member member = new Member();
member.setId(1L);
member.setName("HelloA");
em.persist(member);
tr.commit();
em.close();
emf.close();
}
}
5. 실행 결과
- 위의 코드를 실행하면 아래의 사진과 같은 결과가 콘솔에 출력된다.
- 앞서 설명한 JPA 옵션에 의해, JPA가 어떤 동작(SQL)을 수행했는지 확인할 수 있다.
a) persistence.xml 옵션
// 1. show_sql = SQL 쿼리를 출력한다.
<property name="hibernate.show_sql" value="true"/>
// 2. format_sql = 위의 사진처럼 SQL 쿼리를 정렬한다.
<property name="hibernate.format_sql" value="true"/>
// 3. use_sql_comments = /**/ 내부에 작성된 내용, 해당 쿼리가 수행한 동작을 설명한다.
<property name="hibernate.use_sql_comments" value="true"/>
6. 기본적인 예외 처리
- JPA는 Transaction 단위로 비즈니스 로직을 처리한다.
- 그러므로 예외가 발생하는 경우, Transaction 또한 오류가 있는 것이므로 rollback 해야 한다.
- 이러한 예외를 처리 수행하기 위해서 JPA는 try ~ catch 문을 사용한다.
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();
}
}
- 위의 코드 구조가 정석적인 JPA를 사용한 트랜잭션 하나의 동작 과정을 코드로 표현한 것이다.
- 그러나 사실 try ~ catch 또는 finally 구문에 들어간 내용들을 따로 작성할 필요가 없다.
- 왜냐면 Spring을 사용하는 경우, Spring이 알아서 close() 해주기 때문이다.
7. 기본적인 CRUD 메서드
- JPA를 사용함으로써 더 이상 SQL 중점적인 개발을 하지 않는다.
- 이 말은 개발자가 더 이상 SQL을 작성하는데 시간을 사용하지 않는다는 것이다.
- 그렇다면 JPA는 SQL을 없이 개발자가 CRUD를 사용할 수 있도록 하는 것일까?
- JPA 내부에는 이미 CRUD를 메서드로 만들어 놓았기 때문이다.
- EntityManager 내부에는 기본적인 CRUD 외 다양한 쿼리 메서드를 보유하고 있다.
- 개발자는 이를 가져다 호출하기만 하면 된다.
- 이로써 JPA를 통해 OOP 중점적인 개발이 가능해진다.
8. JPQL(Java Persistence Query Language)
- JPA를 사용하면 개발자는 따로 SQL을 작성하지 않고 OOP 중점적으로 개발한다.
- 그러나, 다양한 조건을 부여한 작업을 수행하는 경우 JPA 내부에 정의된 메서드가 기능적으로 부족한 경우가 있다.
- 그렇다면 직접 쿼리를 작성하여 사용할 수밖에 없는데 JPA에서는 쿼리를 작성할 수 없다.
- 이때 사용할 수 있는 것이 JPQL이다.
- 일반적으로 쿼리(SQL)라고 하면 RDB에서 작성하는 방식을 생각한다.
- RDB의 쿼리와 매우 유사한 문법을 가지지만, JPQL은 객체 중심적으로 쿼리를 작성한다.
- SQL의 경우에는 DB 테이블을 조회하지만, JPQL은 객체(= Entity)를 조회하기 때문이다.
- JPQL의 최대 장점은 DB 방언을 변경하더라도 기존에 작성된 JPQL을 사용하는데 전혀 문제가 없다는 것이다.
- 다음 예시를 간단하게 읽어보기를 바란다.
- 객체를 조회한다는 것이 어떤 것인지 가볍게 이해하도록 하자.
List<Member> result = em.createQuery("select m from Member as m", Member.class).getResultList();
for (Member member : result) {
System.out.println("member.getName() = " + member.getName());
}
9. 마치며...
- 여기까지 JPA의 기본적인 구조와 기능에 대해서 알아보았다.
- 이와 같은 구조를 바탕으로, 다양한 기능이 합쳐져서 온전한 객체지향적 개발이 가능하게 된다.
- 이번 포스팅은 JPA 기본 개념에 대한 간략한 설명에 불과하다.
- 앞으로 하나씩 깊이 있게 배워보자.
'Back-end > JPA 개념' 카테고리의 다른 글
5. Field(칼럼) Mapping (0) | 2022.03.28 |
---|---|
4. Entity Mapping (0) | 2022.03.25 |
3. JPA의 내부구조와 동작 (0) | 2022.03.24 |
1. JPA의 등장 (0) | 2022.03.22 |
0. JPA를 사용하는 이유 (0) | 2022.03.21 |
댓글