- Today
- Total
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- SpringBoot
- Java
- Kotlin
- 책
- EntityTransaction
- chrome80
- 스프링
- jenkins
- docker
- websocket
- infra
- 캐싱전략
- 리뷰
- spring
- container
- JPA
- 만들면서 배우는 클린 아키텍처
- LazyInitialization
- 젠킨스
- 브랜치전략
- 팀네이버 공채
- redis
- SPRING JWT
- Project
- 후기
- JWT
- 팀네이버
- 프로젝트
- network
- Spring Security
PPAK
[Spring/JPA] EntityManagerFactory, EntityManager, EntityTransaction 에 대해서 본문
[Spring/JPA] EntityManagerFactory, EntityManager, EntityTransaction 에 대해서
PPakSang 2022. 7. 6. 00:57
이전 포스팅 에서 JPA 가 자바 진영의 ORM 기술 표준이라는 것과, 성능 최적화를 위해 내부적으로 영속성 컨텍스트(Persistence Context) 를 사용한다는 것을 알아보았다.
이번 포스팅에서는 JPA 를 실제로 사용하기 위한 환경설정과 어떤 흐름으로 DB 와의 연결을 가져오고, Query 를 전송하는지 알아보도록 하겠다.
개발환경 셋팅
Build 도구로는 Maven 을 선택하였다.
Java : 1.8
Hibernate : 5.3.10
H2 DBMS : 1.4.199
아래의 pom.xml (Project Object Model) 통해 의존성을 포함한 빌드 정보 확인
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>sample.ppaksang</groupId>
<artifactId>jpa-prac</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.3.10.Final</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.199</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>
아래는 java 11 사용시 발생할 수 있는 오류를 제어해줄 api 의존성
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
다음으로는 Persistence 객체가 정상적으로 EntityManagerFactory 를 생성할 수 있도록 Meta Data 를 작성해주어야 한다.
프로젝트 내의 META-INF 하위 persistence.xml 를 통해
1. JDBC 의 속성(Driver, Username, PW, DB URL)
2. Hibernate 의 속성
을 추가할 수 있다.
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">
<!--jpa 이름-->
<persistence-unit name="ppaksang-persistence">
<properties>
<!-- 필수 속성, database 접근 정보
javax 로 시작하는 설정은 java 표준-->
<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.jdbc.batch_size" value="5"/>-->
<property name="hibernate.hbm2ddl.auto" value="create" />
</properties>
</persistence-unit>
</persistence>
간략하게 설정을 살펴보면
persistence-unit 단위로 하나의 Persistence 객체의 Meta Data 를 명시가능하다
name 을 통해 Persistence 객체가 설정정보를 식별할 수 있다.
JPA 는 jdbc api 를 직접호출해주기 때문에 이에 대한 기본적인 정보가 명시되어야 한다.
Hibernate 같은 경우에는 자동생성된 sql 문을 출력하는 설정을 추가하였다.
dialect 의 경우에는 DBMS 별 상이한 sql 문법을 보정해주는 설정이다.
위와 같은 개발환경을 셋팅하고 나면, Persistence 객체는 persistence.xml 을 참고해서 EntityManagerFactory 를 생성한다.
기본적으로 EntityManagerFactory 는 애플리케이션 당 1개 생성하도록 설계된다.
EntityManagerFactory 는 연결된 설정정보에 명시된 데이터베이스와 연결하고, 클라이언트의 요청이 들어올 때 마다 connection pool 에 여유 connection 이 존재하는지 확인하고, EntityManager 를 생성해 connection 과 연결해준다.
EntityManager
데이터베이스의 테이블에 매핑되는 객체가 Entity 이다.
이러한 Entity 를 관리하는 기능을 수행하는 객체가 EntityManager 이고, EntityManager 는 요청 쓰레드 1개에만 제공될 수 있다.
EntityManager 는 DB 와의 실제 connection 을 가지고 Transaction 을 수행하기 때문에 여러 쓰레드가 공유하게되면 동시성 문제가 발생할 수 있다.
EntityTransaction
기본적으로 데이터베이스에 대한 접근은 Transaction 단위로 명령이 처리된다.
JPA 는 이러한 Transaction 단위로 처리되지않는 명령에 대해서는 TransactionRequiredException 을 일으킨다.
EntityManager 는 EntityTransaction 을 얻을 수 있으며, transaction 이 begin 되고 commit 되는 사이에 데이터베이스로의 접근이 가능하다
Transaction 내부에서 에러가 발생한 경우에는 꼭 rollback 을 수행해야한다.
Persistence Context
EntityManager 는 본인의 생명주기 동안 관리 가능한 Persistence Context 와 연결된다.
Persistence Context 내부에는 1차 캐시 역할을 하는 공간과, 쓰기 지연 SQL 저장소가 존재한다.
이전 포스팅에서 언급했듯 이러한 Persistence Context 는 JPA 의 성능을 최적화 하기 위해(DB connection 을 최소화 하기 위해) 고안되었다.
아래의 간단한 예시 코드를 통해 위에서 설명한 내용들을 정리해보자
import javax.persistence.*;
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
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;
}
public Team getTeam() {
return team;
}
public void setTeam(Team team) {
this.team = team;
}
}
import javax.persistence.*;
import java.util.List;
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members;
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;
}
public List<Member> getMembers() {
return members;
}
public void setMembers(List<Member> members) {
this.members = members;
}
}
간단한 Member 와 Team Entity 를 생성하고, 하나의 Team 은 다수의 Member 를 가지는 연관관계를 맺는다.
실전에서는 setter 를 남용하지 않지만, 위에선 예제를 위해 모두 설정해준다.
public class JPATest {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("ppaksang-persistence");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member a = new Member();
a.setName("PPakSang");
System.out.println("====");
em.persist(a);
System.out.println("====");
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
위에서 언급한
1. Persistence 객체를 통한 EntityManagerFactory 생성
2. EntityManager 생성
3. Transaction 획득
4. Transaction 내부에서 DB 접근 코드 작성 및 예외 발생 시 rollback 진행
를 모두 수행하였고, 한 가지 주의해야할 것은
꼭 마지막에 em.close() 를 통해서 EntityManager 가 가지고 있는 DB connection 을 반환할 수 있도록 해야한다.
위의 예제를 통해 정상적으로 데이터베이스에 쿼리가 날아감을 확인할 수 있다.
한 가지 흥미로운 점은 insert 쿼리가 날아간 시점인데, 두 ==== 가 모두 출력되고 나서 전송됨을 알 수 있다
여기서 em 이 쓰기 지연 SQL 저장소를 사용하고 있다는 것을 알 수 있다.
엄밀히 이야기하자면 Id 를 생성하는 strategy 가 Sequence(DBMS 의 Sequence 와 같음) 이기 때문에, 현재 sequence number 를 받아왔기 때문에 쓰기 지연이 가능하다 (allocation size 까지는 id 가 확보가 되기 때문)
strategy 를 IDENTITY (auto_increment) 로 설정한다면, insert 쿼리 생성시 곧바로 날아감을 알 수 있다. (매 insert query 마다 id 값을 얻어와야하기 때문)
실제로 쿼리 생성을 호출(em.persist()) 하고 나서, 실제로 쿼리가 나가는 시점은 em.flush() 가 호출될 때 이다.
위의 경우(IDENTITY) 에는 아주 예외적으로 있는 방식이고, 보통의 경우에는 Transaction 이 commit 되는 시점에 em.flush 가 자동적으로 호출된다.
다시 아래의 코드를 살펴보자
public class JPATest {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("ppaksang-persistence");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Team team = new Team();
team.setName("Team1");
em.persist(team);
Member member = new Member();
member.setName("PPakSang");
member.setTeam(team);
em.persist(member);
System.out.println(em.find(Member.class, member.getId()).getName());
System.out.println(em.find(Team.class, team.getId()).getName());
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
위의 코드에서 insert, select 쿼리는 총 몇 번 날아갈까?
persist 2번에 find 2 번이니 총 4번이 날아갈까?
정답은 2번이다. 왜 그런지 이유가 궁금하다면 이전 JPA 포스팅에서 1차 캐시가 운용되는 방식을 참고한다면 이해할 수 있을 것이다.
최종적으로 insert 쿼리가 2번 나가게 되고, 1차 캐시에 저장된 두 Entity 객체를 id 를 통해서 조회하기 때문에 별도로 select query 가 나갈 필요가 없어진다.
잘못된 정보가 있다면 댓글로 알려주시면 감사하겠습니다!!
'spring' 카테고리의 다른 글
[Spring/Spring Boot] 서버 https 적용 (Certbot, Let's Encrypt) (0) | 2022.08.27 |
---|---|
[Spring/SpringBoot] SpringBoot 로컬 서버 Https 적용 (0) | 2022.08.17 |
[Spring] Spring Security 에서 JWT 를 통한 인증/인가 수행하기 (7) | 2022.08.07 |
[Spring/SpringBoot] Spring Boot Layered Architecture (0) | 2022.07.31 |
[Spring] Spring 이란? Spring 특징 (IOC/DI/AOP/PSA) (0) | 2022.06.22 |