오늘 학원에서 JPA 를 배웠는데 해당 부분을 복습도 할 겸 블로그에 글을 작성해보려고 한다.
JPA 는 Java Persistence API로, 자바에서 관계형 데이터베이스(RDBMS)를 객체처럼 다룰 수 있게 해주는 ORM 이다.
ORM 은 Object-Relational Mapping 의 약자로 객체지향 프로그래밍 언어의 객체와 관계형 데이터베이스의 데이터를 서로
연결해주는 기술을 의미한다.
현재까지는 데이터베이스를 연동하는 방법으로 XML 기반의 매퍼를 통해 쿼리문을 작성하는 것이 일반적이였다.
하지만 이러한 방식은 번거로움과 유지보수의 어려움이 있었다. 이러한 문제점을 해결하고 객체 중심으로 DB 를 관리하기 위해 등장한 라이브러리가 JPA 이다.
이 JPA를 통해 DB의 데이터들을 객체로 쉽게 만들 수 있고, 또한 DB CRUD(Create, Read, Update, Delete) 작업을 쉽게 수행할 수 있게 됐다.
이제 JPA 를 프로젝트에 적용시키는 방법을 알아보자.
1. Gradle 의존성 추가
- build.gradle 파일에 JPA 관련 의존성을 추가하여 프로젝트에서 JPA 라이브러리를 사용할 수 있도록 설정한다.
(일반적으로 Spring Data JPA 를 함께 사용한다.)
// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-jpa
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
2. YML 설정
- application.yml 혹은 application.properties 파일에 DB 연결 정보 및 JPA 관련 설정을 추가한다.
- 디폴트는 properties 지만, yml이 더 직관적으로 설정할 수 있기 때문에 필자는 yml 로 바꿨다.
jpa:
show-sql: true
hibernate:
ddl-auto: create
properties:
hibernate:
dialect: org.hibernate.dialect.H2Dialect
format_sql: tru
해당 부분을 작성하면 문제 없이 JPA 를 사용할 준비가 되었다 !
엔티티와 DTO : JPA 의 핵심
JPA 를 효과적으로 사용하기 위해서는 엔티티와 DTO 에 대한 이해가 필요하다.
✅엔티티 (Entity)
- @Entitiy 어노테이션으로 선언되는 클래스로, 실제 DB 테이블의 구조를 객체 형태로 표현한다.
- 각 엔티티 객체는 테이블의 하나의 레코드와 매핑된다.
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
// Getter, Setter 및 기타 필드/메서드 생략
}
✅DTO (Data Transfer Object)
- 클라이언트와 서버 간에 데이터를 전송하는데 사용되는 객체다.
- 엔티티의 모든 필드를 그대로 사용할 수 있지만, 특정 상황에 맞춰 필요한 필드만 포함하여 데이터를 효율적으로 주고 받을 수 있도록 설계하면 된다.
public class MemberDto {
private String name;
private String email;
// Getter, Setter 및 생성자 생략
}
영속성 관리 (Persistence Management)
JPA 의 장점중 하나는 영속성 관리 (Persistence Management) 다.
JPA 는 영속성 컨텍스트라는 특별한 공간을 사용하여 엔티티 객체의 생명주기를 관리하고 DB 와의 상호작용을 자동화한다.
개발자는 단순히 엔티티 객체의 상태를 변경하거나 영속성 컨텍스트에 저장하기만 한다면, JPA 가 알아서 적절한 SQL 쿼리를
생성하고 실행하여 DB 에 반영해준다. 우리는 마치 객체만 관리하는 것처럼 느껴지지만, 사실 JPA 가 복잡한 작업을 처리해주는 것이다.
엔티티 매니저 (Entity Manager)
영속성 컨텍스트를 관리하고 엔티티 객체들과 상호작용 하기 위해서는 엔티티 매니저가 필요하다.
엔티티 매니저는 영속성 컨텍스트와 개발자를 연결해주는 통로라고 생각하면 된다.
EntityManager em = emf.createEntityManager();
em.getTransaction().begin(); //트랜잭션을 시작
Member member = new Member("member1");
em.persist(member); // 영속 상태로 전환
Member foundMember = em.find(Member.class, "member1"); // 조회
foundMember.setName("Updated Name"); // Dirty Checking 발생
em.getTransaction().commit(); // 플러시 → DB 반영
엔티티 매니저는 JPA 라이브러리 안에 속해있는 인터페이스이다. 따라서 우리가 사용하고 싶은 클래스에서 구현하면 된다.
한 코드씩 살펴보겠다.
EntityManager em = emf.createEntityManager();: 엔티티 매니저 팩토리를 사용하여 엔티티 매니저를 생성한다. 각 트랜잭션 단위 또는 요청 단위로 엔티티 매니저를 생성하는 것이 일반적이다.
em.getTransaction().begin();: 현재 엔티티 매니저와 연결된 트랜잭션을 시작한다. 데이터베이스의 변경 작업을 수행하기 위해서는 트랜잭션이 시작되어야 한다.
Member member = new Member("member1");: 새로운 Member 객체를 생성하고 초기화한다. 이 객체는 아직 영속성 컨텍스트에 의해 관리되지 않는 비영속 (new/transient) 상태다.
em.persist(member);: persist() 메서드를 호출하여 member 객체를 영속성 컨텍스트에 저장한다. 이 시점부터 member 객체는 영속 (managed) 상태가 된다. JPA는 이 객체의 변경 사항을 추적하고, 트랜잭션 커밋 시점에 데이터베이스에 반영할 준비를 한다.
Member foundMember = em.find(Member.class, "member1");: find() 메서드를 사용하여 Member 클래스 타입과 기본 키 값 "member1"에 해당하는 엔티티를 영속성 컨텍스트에서 조회한다. 만약 영속성 컨텍스트에 해당 엔티티가 존재하면 1차 캐시에서 조회하고, 없다면 데이터베이스에서 조회 후 영속성 컨텍스트에 저장하고 반환한다.
foundMember.setName("Updated Name");: 조회된 foundMember 객체의 이름을 변경한다. 이 시점에서 JPA는 영속 상태의 엔티티의 변경을 감지하는 더티 체킹 (Dirty Checking) 메커니즘이 작동한다. 영속성 컨텍스트는 스냅샷을 보관하고 있다가 트랜잭션 커밋 시점에 엔티티와 스냅샷을 비교하여 변경된 부분을 찾아낸다.
em.getTransaction().commit();: 현재 트랜잭션을 커밋한다. 이 과정에서 다음과 같은 일들이 순차적으로 발생한다.
- 플러시 (Flush): 영속성 컨텍스트의 변경 내용 (여기서는 member 객체의 INSERT 쿼리와 foundMember 객체의 UPDATE 쿼리)을 데이터베이스에 동기화한다. 실제 SQL 쿼리가 데이터베이스로 전송되어 실행된다.
- 트랜잭션 반영: 데이터베이스 트랜잭션을 실제 데이터베이스에 반영한다.
트랜잭션 관리
데이터베이스 작업은 논리적인 단위로 묶여야 하며, 이를 트랜잭션이라고 한다. JPA 에서는 트랜잭션을 엔티티 매니저를 통해 관리한다. 트랜잭션이 중요한 이유는 데이터베이스의 무결성과 일관성을 보장하기 위함이다.
우리가 SQL 에서 DML 명령어를 사용할 때, 한줄한줄씩 커밋한 기억이 없다. 하나의 트랜잭션이 무조건 하나의 쿼리문을 뜻하는 게 아니기 때문이다. 그래서 우리는 INSERT 여러줄, SELECT 여러줄, UPDATE 여러줄 등 이런식으로 여러개의 쿼리문을 작성하고 그 후에 커밋을 해서 트랜잭션을 종료한다. JPA 에서도 동일하게 사용하면 된다.
EntityTransaction tx = em.getTransaction();
try {
tx.begin(); // 트랜잭션 시작
// CRUD 작업 수행
Member member = new Member();
member.setName("새로운 멤버");
em.persist(member);
tx.commit(); // 트랜잭션 커밋 (flush 자동 호출)
} catch (Exception e) {
tx.rollback(); // 오류 발생 시 트랜잭션 롤백
} finally {
em.close(); // 엔티티 매니저 종료
}
'Spring' 카테고리의 다른 글
| [Java] Dependency Injection에 대해서, 그리고 왜 사용할까? (0) | 2025.05.21 |
|---|---|
| [Spring] Spring 프레임워크란 무엇일까? (2) | 2025.05.20 |
| [Spring] ResponseEntity 와 RESTful API 설계에 대해 (0) | 2025.05.19 |
| [Spring] Spring 프레임워크에서 자주 사용되는 인터페이스, 어노테이션 정리 (0) | 2025.05.13 |
| [Java] 어노테이션은 무엇일까? (1) | 2025.04.28 |