0. 도입
안녕하세요, [JPA ORM 기본]의 두 번째 시리즈입니다.
이번 포스팅에서는 JPA를 위한 기본 설정을 진행한 뒤 엔티티 매니저를 간단히 구현하며 실습해 보겠습니다.
1. 버전 정보
- Java 21 버전
- 스프링부트 3.3.7
- Gradle
- H2 Database
- Spring Data Jpa
- Lombok
2. 프로젝트 생성 및 실행
DB 설치하기
프로젝트 생성
위 사이트에서 스프링부트 프로젝트를 간단히 생성할 수 있습니다.
이름을 간단하게 지어주고, 의존성 부분에 필요한 세 가지 의존성을 추가하겠습니다.
설정을 마쳤다면 다운받고 압축을 풀어 주세요.
의존성 추가
위에서 그래들로 설정했으므로 그래들 관련 설정 파일은 다음과 같아야 합니다.
Build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '3.3.7'
id 'io.spring.dependency-management' version '1.1.7'
}
group = 'com.wondrous'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
compileOnly 'org.projectlombok:lombok'
}
tasks.named('test') {
useJUnitPlatform()
}
메이븐일 경우 아래처럼 설정하면 됩니다.
<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_1.xsd"
version="3.1">
<persistence-unit name="hello">
<class>hellojpa.Member</class>
<properties>
<property name="jakarta.persistence.jdbc.url" value="jdbc:h2:mem:testdb"/>
<property name="jakarta.persistence.jdbc.user" value="sa"/>
<property name="jakarta.persistence.jdbc.password" value=""/>
<property name="jakarta.persistence.jdbc.driver" value="org.h2.Driver"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
</properties>
</persistence-unit>
</persistence>
JPA 관련 정보 설정
프로젝트 관련 설정을 통해 JPA의 필수적인 정보를 설정합니다. 먼저 설정 파일부터 살펴봅시다.
application.yml
spring:
application:
name: JPA-Hands-On
datasource:
driver-class-name: org.h2.Driver
username: sa
password: 1234
url: jdbc:h2:tcp://localhost/~/study/JPA/ORM-Hands-On
jpa:
hibernate:
ddl-auto: create
properties:
dialect: org.hibernate.dialect.H2Dialect
hibernate:
highlight_sql: true
use_sql_comments: true
format_sql: true
logging.level:
org.hibernate.type: trace
org.hibernate.SQL: debug
org.hibernate.orm.jdbc.bind: trace
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="1234"/>
<property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/study/JPA/ORM-Hands-On"/>
<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.highlight_sql" value="true"/>
<!--<property name="hibernate.hbm2ddl.auto" value="create" />-->
</properties>
</persistence-unit>
</persistence>
이 설정을 통해 DB 연결 정보, JPA 구현체 설정, 영속성 유닛(persistence unit) 정의 등 JPA 관련 설정합니다.
잠깐 영속성 유닛에 대해 정리해보면,
영속성 유닛이란?
응용 프로그램에서 EntityManager 인스턴스가 관리하는 모든 엔티티 클래스의 집합
JPA는 기본적으로 persistence.xml
파일을 통해 영속성 유닛(persistence unit)을 설정하도록 설계되었습니다.
설정 규칙
- 데이터베이스당 하나씩 설정
- persistence.xml 파일에서 엘리먼트로 정의
- 각 영속성 유닛은 고유한 이름을 가져야 한다.
영속성 유닛의 역할
- 데이터베이스 연결 정보 제공: 특정 데이터베이스에 대한 연결 정보를 포함
- 엔티티 클래스 관리: 해당 데이터베이스와 매핑될 엔티티 클래스들을 지정
- EntityManagerFactory 생성: 영속성 유닛 정보를 바탕으로 EntityManagerFactory를 생성
환경별 영속성 유닛 설정
스프링부트와 순수 자바 환경에서 영속성 유닛 설정 방식은 살짝 다릅니다.
- Spring Boot 환경에서는 자동 구성 기능을 통해
application.properties
또는application.yml
파일의 설정을 자동으로 JPA 설정에 적용한다. - 순수한 Java 환경에서 JPA를 사용할 때는
persistence.xml
파일을 통해 JPA 설정을 인식한다.
이번 글에서는 순수 Java 환경에서 간단히 엔티티매니저를 직접 실행하므로 XML 파일로 설정해야 합니다. persistence.xml 파일은 일반적으로 프로젝트의 META-INF 디렉토리에 위치하며, JPA 구현체(Hibernate)가 이 파일을 읽어 필요한 설정 정보를 로드한다.
하지만 이후 실습할 주문 시스템에서는 application.yml로 설정정해야 하니 두 방법으로 설정한 뒤 필요한 설정을 골라 사용하겠습니다.
하이버네이트 설정
이전 글에서 간단히 하이버네이트를 소개했습니다. 간단히 짚고 넘어가면, 하이버네이트는 JPA의 구현체입니다.
위의 속성에서 hibernate로 시작하는 설정은 hibernate에서 인식하는 속성으로 다양한 설정을 제공합니다.
- DB 연결 설정
- DB 방언 설정
- SQL 관련 설정
- 스키마 자동 생성
- 성능 최적화
참고로 하이버네이트는 40가지 이상의 DB 방언 설정을 제공합니다. 이런 설정들을 통해 JPA는 DB에 상관없이 독립적으로 사용할 수 있습니다.
프로젝트 실행
HelloJPA.java
package com.wondrous.JPA_Hands_On;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;
public class HelloJPA {
public static void main(String[] args) {
System.out.println("hello jpa!!");
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
emf.close();
em.close();
}
}
- System.out.println("hello jpa!!"): 기본적인 "hello jpa!!" 출력 코드
- createEntityManagerFactory("hello")앞에서 설정한 "hello"이름을 가진 엔티티매니저 팩토리를 실행
- createEntityManager(): 엔티티매니저 팩토리로부터 엔티티매니저를 가져오는 코드
위 코드를 실행하면 콘솔에 다음과 같이 성공 결과가 출력됩니다.
정상적으로 "hello jpa!!"가 출력되었고, persistence.xml에서 설정된 정보, 하이버네이트에서 설정한 DB 연결 정보와 방언 정보, 연결 풀 설정 등이 정상적으로 인식되어 콘솔에 출력됩니다.
이제 JPA를 간단히 실습하며 CRUD를 살펴보겠습니다.
2. JPA 동작 확인
DB 테이블 생성
먼저 DB에 접속하여 MEMBER 테이블을 SQL로 직접 생성해야 합니다.
create table Member (
id bigint not null,
name varchar(255),
primary key (id)
);
CRUD 테스트
CRUD는 트랜잭션 내에서 엔티티 매니저를 통해 진행됩니다.
생성(Create)
package com.wondrous.JPA_Hands_On;
import com.wondrous.entity.Member;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import jakarta.persistence.Persistence;
public class HelloJPA {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member member1 = new Member(1L, "John");
Member member2 = new Member(2L, "Jaden");
em.persist(member1);
em.persist(member2);
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
- em.persist(member1) :엔티티 매니저에 영속성을 추가해주는 로직
EntityManager에 persist로 영속성을 추가해주면 트랜잭션이 커밋하는 시점에 영속성 컨텍스트의 내용을 DB에 반영합니다.
코드를 실행 결과, 위처럼 자동으로 SQL이 실행되고 DB에 영속성을 추가했던 Member 엔티티들이 저장된 것을 확인할 수 있습니다.
이제 조회 기능을 테스트해보면,
조회(Read)
...
try {
Member findMember = em.find(Member.class, 1L);
System.out.println("findMember: " + findMember.toString());
tx.commit();
}
...
try 부분을 제외한 앞뒤는 앞에서 다룬 Create와 동일합니다.
- em.find(Member.class, 1L): 엔티티매니저를 통해 1L의 엔티티를 조회하는 코드
엔티티 매니저를 통해 조회하면 처음에는 영속성 컨텍스트에서 조회하고 없다면 DB에서 조회합니다.
위의 그림에서처럼, 영속성 컨텍스트에 해당 엔티티가 없으므로 SQL을 통해 DB에서 조회한 것을 볼 수 있습니다.
다음으로 수정 기능을 테스트해보면,
수정(Update)
...
try {
Member findMember = em.find(Member.class, 1L);
findMember.setName("SpiderMan");
System.out.println("updatedMember: " + findMember.toString());
tx.commit();
}
...
- em.find(Member.class, 1L): Member을 조회 후 영속성 컨텍스트에 저장
- findMember.setName("SpiderMan"): 엔티티 수정
영속성 컨텍트에 저장된 엔티티를 수정하면, 트랜잭션 커밋 시점에 변경 내용이 수정됩니다.
첫 번째 쿼리는 영속성 컨텍스트에 엔티티가 없이 때문에 SELECT 쿼리가 날아갔고,
두 번재 쿼리는 영속성 컨텍스트의 수정된 내용을 반영하기 위해 UPDATE 쿼리가 날아갔습니다.
DB에서 직접 테이블을 확인해보면 변경된 이름이 정상적으로 반영되었습니다.
마지막으로 기능을 테스트해보면,
삭제(Delete)
...
try {
Member findMember = em.find(Member.class, 2L);
em.remove(findMember);
tx.commit();
}
...
- em.find(Member.class, 2L): 엔티티를 조회
- em.remove(findMember): 조회한 엔티티를 삭제
먼저 해당 엔티티를 찾기 위한 SELECT 쿼리가 정상적으로 수행되었고,
다음으로 DELETE 쿼리가 수행되어 영속성 컨텍스트에서 제거된 내용이 반영되었습니다.
DB에 접속하여 확인해보면 테이블에 데이터가 정상적으로 삭제되었습니다.
이제 엔티티매너저가 CRUD를 어떻게 실행하는지 정리해보면,
3. 엔티티 매니저의 동작 원리
동작 원리
- JPA의 구현체인 JDBC가 먼저 설정 정보를 확인하고 이를 바탕으로 DB를 인식한다.
- 이후 EntityManagerFactory를 실행하고 이 안에서 EntityManager를 관리한다.
- 트랜잭션 커밋 시점에서 변경 부분을 확인하고 반영한다. 이 시점에서 실제 CRUD에 대한 SQL 쿼리가 날아간다.
주의 사항
- 엔티티매니저 팩토리는 DB당 하나만 생성
- 엔티티매니저는 고객의 요청마다 사용된다.
- 엔티티 매니저는 쓰레드간에 공유 x(사용 후 버려야 한다.)
- JPA의 모든 데이터 변경은 트랜잭션 안에서 실행
이 주의사항은 매우 중요합니다.
만약 다른 엔티티 매니저가 동시에 DB에 접근하면?
예를 들어 은행 계좌에서 돈을 출금하는 트랜잭션이 있다고 하면, 이 트랜잭션이 진행되는 동안에는 오직 하나의 엔티티 매니저만(em1)이 이 계좌 정보에 접근해야 합니다.
계좌에서 10만원을 출금하는 트랜잭션을 경우를 보면,
- 10만원 이상의 돈이 있는지 확인
- 내 계좌 DB에서 10만원을 제거
- 다른 계좌에 10만원을 추가
만약 다른 엔티티 매니저(em2)도 이 계좌에 접근하여 같은 과정을 반복하면 두 엔티티 매니저는 모두 계좌에 돈이 충분하다고 판단하여 각각 10만원씩 인출하고 트랜잭션 종료 시점에 DB에서 10만원을 제거합니다.
결국 계좌에는 10만원이 있었음에도 총 20만원이 빠져나가는 대참사가 발생합니다.
4. 마무리
요약
지금까지 기본적인 프로젝트를 생성하고 엔티티 매니저에 대해 간단히 실습해 보았습니다.
참고 자료
[(강의)자바 ORM 표준 JPA 프로그래밍 - 기본편 강의]
다음 글에서는 기본키 매핑에 대해 다루어보겠습니다.
긴 글 읽어주셔서 감사합니다. 궁금한 부분이나 오류가 있으면 댓글 남겨주세요.
도움이 되었다면 좋아요 부탁드립니다.
'SpringBoot > JPA' 카테고리의 다른 글
[JPA ORM 기본] (1) JPA란? (0) | 2024.12.30 |
---|---|
[JPA ORM 기본] (0) 시리즈 소개 (0) | 2024.12.27 |
경이로운 BE 개발자가 되기 위한 프로그래밍 공부 기록장
도움이 되었다면 "❤️" 또는 "👍🏻" 해주세요! 문의는 아래 이메일로 보내주세요.