JPAUtil 클래스는 JPA를 사용할 때 핵심이 되는 EntityManagerFactory (EntityManager를 생성성)관리하는 유틸리티 클래스로 EntityManagerFactory 싱글톤 관리 / 리소스 정리 / 코드 중복 제거 등의 역할을 담당
✍️ 작성
package org.example.jpa;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class JPAUtil {
// EntityManagerFactory를 싱글톤으로 생성 (UserPU는 persistence.xml에 정의된 persistence-unit 이름)
private static final EntityManagerFactory emfInstance =
Persistence.createEntityManagerFactory("UserPU");
// 애플리케이션 종료 시 자동으로 EntityManagerFactory를 닫기 위한 Shutdown Hook 등록
static {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
if (emfInstance != null) {
log.info("---- emf close ---");
emfInstance.close();
}
}));
}
// 외부에서 객체 생성을 막기 위해 private 생성자 사용
private JPAUtil() {}
// DAO나 서비스 계층에서 EntityManagerFactory를 얻을 때 사용하는 메소드
public static EntityManagerFactory getEntityManagerFactory() {
return emfInstance;
}
}
싱글톤 관리
emfInstance는 클래스 로딩 시 한 번만 생성돼서 모든 DAO가 동일한 인스턴스를 공유하게 됨. 이렇게 하면 리소스 낭비를 줄이고, 여러 DAO에서 일관되게 EntityManager를 사용 가능
Shutdown Hook
static 블록 내에서 Shutdown Hook을 등록해서 애플리케이션 종료 시 자동으로 emfInstance.close()를 호출함. DAO에서는 이 부분에 대해 신경 쓸 필요 없이, 리소스 정리가 안전하게 이루어짐.
캡슐화
생성자를 private으로 만들어서 외부에서 인스턴스화를 방지. 오직 getEntityManagerFactory()를 통해서만 EntityManagerFactory에 접근가능
DAO들은 데이터베이스 작업에 집중할 수 있고, 인프라스트럭처 관련 코드 (데이터베이스 연결 관리, 트랜잭션 처리, 로깅, 보안 설정 등)는 분리돼서 유지보수나 확장이 용이해짐
UserDAO 클래스는 JPA를 이용해서 User 엔티티에 대한 CRUD(Create, Read, Update, Delete) 작업을 담당하는 DAO(Data Access Object) 역할
✍️ 작성
package org.example.jpa;
import jakarta.persistence.EntityManager;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class UserDAO {
// CRUD 작업마다 EntityManager를 새로 생성해서 사용
// JPAUtil.getEntityManagerFactory()를 통해 공용 EntityManagerFactory를 가져오고,
// 각 작업에 필요한 EntityManager를 생성함.
// 1. Create (생성)
public void createUser(User user) {
EntityManager entityManager = JPAUtil.getEntityManagerFactory().createEntityManager();
try {
// 트랜잭션 시작
entityManager.getTransaction().begin();
// 새 User 객체를 데이터베이스에 저장
entityManager.persist(user);
// 트랜잭션 커밋 (변경사항 반영)
entityManager.getTransaction().commit();
} finally {
// 작업이 끝나면 EntityManager를 꼭 닫아줘야 함
entityManager.close();
}
}
// 2. Update (수정)
public void updateUser(User user) {
EntityManager entityManager = JPAUtil.getEntityManagerFactory().createEntityManager();
try {
entityManager.getTransaction().begin();
// merge()는 기존 엔티티를 업데이트하거나, 없으면 저장해주는 역할을 함.
entityManager.merge(user);
log.info("update :: ok");
entityManager.getTransaction().commit();
} finally {
entityManager.close();
}
}
// 3. Delete (삭제)
public void deleteUser(User user) {
EntityManager entityManager = JPAUtil.getEntityManagerFactory().createEntityManager();
try {
entityManager.getTransaction().begin();
// remove()는 관리 중인(entityManager에 의해 관리되는) 엔티티만 삭제할 수 있음.
// 만약 전달받은 user 객체가 관리 상태가 아니면 merge()를 통해 관리 상태로 만든 뒤 삭제함.
entityManager.remove(entityManager.contains(user) ? user : entityManager.merge(user));
entityManager.getTransaction().commit();
} finally {
entityManager.close();
}
}
// 4. Read (조회)
public User findUser(Long id) {
EntityManager entityManager = JPAUtil.getEntityManagerFactory().createEntityManager();
try {
// find() 메소드를 통해 id에 해당하는 User 엔티티를 조회.
return entityManager.find(User.class, id);
} finally {
entityManager.close();
}
}
}
EntityManager 생성
각 메소드에서 JPAUtil.getEntityManagerFactory().createEntityManager()로 새 EntityManager를 생성. 여기서 EntityManager는 JPA에서 엔티티를 관리하고 데이터베이스 작업을 수행하는 주된 인터페이스임.
트랜잭션 관리
DB에 변경을 주는 작업은 트랜잭션 내에서 수행하며 entityManager.getTransaction().begin()으로 시작하고, 작업 완료 후 commit() 호출.
엔티티 상태 관리
persist(user) - 신규 엔티티 저장.
merge(user) - detached 상태의 엔티티를 관리 상태로 복원해 업데이트.
remove(...) - 삭제할 때, 관리 상태가 아니면 contains(user)로 확인 후 merge(user)로 관리 상태로 만든 뒤 삭제.
자원 정리 - 모든 작업 후 finally 블록에서 entityManager.close()를 호출해 DB 연결 자원을 확실하게 반납.
이 구조 덕분에 DAO는 DB 작업에만 집중할 수 있고, EntityManagerFactory 생성, 트랜잭션 관리 등 인프라 관련 코드는 깔끔하게 분리되서 유지보수가 용이해짐
엔티티는 Java Persistence API (JPA)에서 가장 핵심적인 개념 중 하나로 엔티티는 데이터베이스 테이블의 행(row)에 대응되는 객체지향 모델을 나타낸다.
엔티티는 데이터베이스의 테이블을 객체지향적으로 추상화 한 것으로 각 엔티티 인스턴스는 테이블의 개별 레코드에 해당 하며, 엔티티 클래스의 속성은 테이블의 컬럼과 매핑 된다.
JPA에서는 @Entity, @Table, @Column, @Id 등의 어노테이션을 사용 해 이 매핑 정보를 명시적으로 정의할 수 있어, 데이터베이스 스키마와 객체 모델 간의 불일치를 최소화할 수 있다.
또한, 엔티티를 통해 복잡한 SQL 쿼리를 직접 작성하지 않고도, 객체 지향적인 방식으로 데이터의 생성, 조회, 수정, 삭제(CRUD) 작업을 수행 할 수 있으므로 코드의 가독성과 유지보수성이 크게 향상된다.
| 조건 | 설명 |
|---|---|
| @Entity 어노테이션 | 클래스가 JPA 엔티티임을 표시하기 위해 반드시 @Entity 어노테이션을 붙여야 함 |
| 식별자 | 각 엔티티는 유일하게 식별될 수 있는 식별자를 가져야 함. 보통 @Id 어노테이션을 사용해 필드를 지정 필요에 따라 @GeneratedValue로 자동 생성도 가능 |
| 기본 생성자 | JPA 구현체가 리플렉션을 통해 엔티티 인스턴스를 생성할 수 있도록 public이나 protected 접근 제어자를 가진 기본 생성자가 필요 |
| 클래스 수준의 제한 | 엔티티 클래스는 final 클래스여서는 안 되고, 변경 가능해야 함. 이 덕분에 추가적인 메서드나 속성을 자유롭게 정의가능 |
✍️ 작성
package org.example.jpa;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
// 이 클래스는 JPA 엔티티임을 나타내며, 데이터베이스의 "schools" 테이블과 매핑
@Entity
@Table(name = "schools")
// 기본 생성자를 자동으로 생성
@NoArgsConstructor
@Getter
@Setter
public class School { // 이 엔티티는 Student와의 연관관계에서 비소유자임 (실제 외래키 관리는 Student 엔티티가 담당)
// 기본 키를 지정하며, DB가 자동으로 값(예: AUTO_INCREMENT)을 생성하도록 함
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 'name' 컬럼은 null 값이 들어갈 수 없도록 제약 설정
@Column(nullable = false)
private String name;
// 일대다 관계: 한 School에 여러 Student가 소속됨.
// mappedBy="school"은 Student 엔티티의 'school' 필드가 연관관계의 주인임을 지정
@OneToMany(mappedBy = "school")
private List<Student> students = new ArrayList<>();
// 이름을 이용해 School 객체를 생성할 수 있는 추가 생성자
public School(String name) {
this.name = name;
}
}
해당 코드의 School 엔티티는 데이터베이스의 "schools" 테이블과 매핑되며, 학교의 고유 ID와 이름을 저장한다.
또한, 한 학교에 여러 학생(Student)들이 소속될 수 있는 일대다 관계를 갖는데, 이 관계의 실제 관리는 Student 엔티티에서 이루어진다.

| 생명주기 | 설명 |
|---|---|
| New | 새로 만든 엔티티 객체 상태로 아직 영속성 컨텍스트(퍼시스턴스 컨텍스트)에 올라가지 않은 상태 persist()를 호출하면 DB에 저장될 준비가 되면서 Managed 상태로 넘어감 |
| Managed | 말 그대로 영속성 컨텍스트가 관리하고 있는 상태 find()나 JPQL로 조회하면, 엔티티가 영속성 컨텍스트 안에 로딩되면서 Managed 상태가 됨 이 상태에서는 엔티티 변경사항이 영속성 컨텍스트에 반영되며 flush() 시점에 DB에 업데이트 추가로 remove()를 호출하면 Removed 상태로, detach()나 clear(), close() 같은 메서드를 호출하면 Detached 상태로 빠져나감 |
| Removed | 말 그대로 제거된 상태 remove()를 호출하면 Managed 상태에서 Removed 상태로 전환 이 상태에서 flush()가 일어나면 DB에서 실제로 데이터가 삭제 하지만 아직 트랜잭션이 끝나지 않았다면 다시 persist() 같은 걸로 Managed로 돌아올 수도 있음. |
| Detached | 원래 Managed였던 엔티티가 더 이상 영속성 컨텍스트에서 관리되지 않는 상태 detach(), clear(), close()로 Managed 엔티티를 강제로 분리하면 이렇게 Detached 상태가 됨. 해당 상태에선 엔티티를 변경해도 영속성 컨텍스트가 몰라서 DB에 반영 되지 않음. 다시 Managed 상태로 만들고 싶으면 merge()를 써야함 |