[JPA] JPA란? + @PrePersist, @PreUpdate

TNFUDS·2025년 11월 6일

FinTrack 프로젝트

목록 보기
3/14

user 테이블을 설계할 때 @PrePersist@PreUpdate 어노테이션을 사용했다.

@PrePersist@PreUpdate 은 Entity가 Persist(데이터베이스에 삽입) 또는 Update(데이터베이스에 수정)되기 전에 JPA Provider가 자동으로 실행해야 하는 메서드를 지정되는데 사용되는 JPA의 어노테이션이다.

@PrePersist

Entity가 영속화(삽입)되기 직전에 실행되어야 하는 메서드 표시할 때 사용
주로 createdAt같은 필드를 자동으로 세팅할 때 사용한다.

@PreUpdate

데이터베이스에서 Entity가 업데이트되기 직전에 실행되어야 하는 메서드 표시하는 데 사용
Entity의 변경사항이 데이터베이스와 동기화되기 전에 JPA Provider에 의해 자동으로 호출된다.
변경 시점을 자동으로 기록할 때 자주 사용된다.



JPA란?

JPA(Java Persistence API) 는

자바 객체(Object)를 데이터베이스 테이블에 자동으로 매핑해주는 ORM(Object-Relational Mapping) 기술 표준이다.

즉, SQL을 직접 쓰지 않고도 자바 객체로 DB 데이터를 다룰 수 있게 해주는 기술이다.
JPA를 사용하면 객체 지향 프로그래밍과 데이터베이스 간의 매핑을 간단하게 처리할 수 있다.

ORM(Object-Relational Mapping)

객체와 관계형 데이터베이스 간의 변환 작업을 자동으로 처리해주는 기술
개발자가 반복적인 SQL을 직접 작성하지 않아도 된다.

반면 SQL Mapper는 객체와 테이블 간 관계를 매핑하는 것이 아니다.
SQL문을 직접 작성하고 쿼리 수행한 결과를 어떤 객체에 매핑할지 바인딩하는 방법이다.
SQL Mapper에는 MyBatis 등이 있다.

JPA vs. MyBatis 비교

JPA는 쿼리를 만들지 않아도 되고, 객체 중심의 개발이 가능하다. 하지만 복잡한 쿼리는 해결이 어렵다.

반면, MyBatis는 자바에서 SQL Mapper 지원하는 프레임워크다.
쿼리문을 xml로 분리하여 작성할 수 있고, 복잡한 쿼리문도 작성할 수 있다.
그러나 객체와 쿼리문 모두 관리해야 하고, CRUD 메소드를 직접 다 구현해야 한다.

구분JPAMyBatis
기본 개념ORM (객체 중심 매핑)SQL Mapper (SQL 중심 매핑)
SQL 작성 여부직접 작성할 필요 없음 (자동 생성)개발자가 SQL 직접 작성
코드량적음 → 생산성 높음많음 → 세밀한 제어 가능
유지보수성엔티티 기반 → 필드명 변경 시 자동 반영SQL 수정 필요 (매핑 수동)
성능 튜닝Hibernate 내부 최적화 활용SQL 직접 최적화 가능
복잡한 쿼리 처리JPQL, QueryDSL 등 사용복잡한 SQL 자유롭게 작성 가능
대표 사용 예시Spring Data JPA, HibernateMyBatis, MyBatis-Spring
학습 난이도ORM 개념 이해 필요 (초반 러닝커브 有)SQL 위주 → 상대적으로 직관적
트랜잭션 관리Spring이 자동 관리직접 제어 가능
적합한 경우도메인 중심 설계(Domain-Driven Design)DB 중심, 복잡한 SQL 중심 서비스


JPA 작동원리

1. DB의 테이블에 대응되는 자바 클래스를 만들어보자.

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String email;
    private String password;
}

위 클래스는 실제로 DB에 존재하지 않지만, JPA가 실행되면 users 테이블과 매핑된다.


2. 영속성 컨텍스트 (Persistence Context)
JPA는 DB와 직접 연결하지 않는 대신, 영속성 컨텍스트라는 가상의 메모리 공간을 만들어 엔티티 객체를 관리한다.

  • EntityManager가 관리한다.
  • Entity 객체를 저장하는 1차 캐시를 갖고 있다.
User user = new User("apple@naver.com", "1234");
entityManager.persist(user);

위 코드를 실행하면
DB에 바로 INSERT되는 것이 아니라
일단 영속성 컨텍스트에 저장된 후 → 트랜잭션이 커밋될 때 실제 SQL이 생성되어 DB에 반영된다.
이를 지연 쓰기(Write-behind)라고 한다.

3. 트랜잭션과 동기화
트랜잭션이 커밋될 때 JPA는 내부적으로 SQL을 생성하고 DB에 반영한다.

entityManager.persist(user);   // 저장 예약
transaction.commit();          // INSERT 실행

4. 변경 감지 (Dirty Checking)
변경 감지는 JPA의 핵심 기능 중 하나이다.
DB에서 가져온 객체를 수정만 해도 자동으로 UPDATE 쿼리가 날아간다.

User user = entityManager.find(User.class, 1L);  // SELECT 실행
user.setEmail("new@naver.com");                  // 값 변경
transaction.commit();                            // UPDATE 실행

이는 JPA가 1차 캐시(영속성 컨텍스트)에 저장된 스냅샷과 현재 엔티티 객체를 비교하여 변경 사항을 감지하기 때문이다.

  • 조회된 엔티티는 “영속 상태”가 되어 JPA가 자동 관리한다.
  • commit 시 flush가 호출되어 변경된 필드를 자동 반영한다.

내부 동작 구조

Controller  →  Service  →  Repository (JPA)
                    ↓
        EntityManager / Hibernate
                    ↓
              Database (SQL)

JPA는 “표준”일 뿐이고, 실제 동작은 Hibernate라는 구현체가 맡는다.
실행 로그에 Hibernate: insert into ... 같은 쿼리가 찍히는 이유도 이 때문이다
위에서 언급한 EntityManager는 Hibernate가 제공하는 핵심 API이며 Spring Data JPA가 이것을 대신 관리한다.


JPA 메서드

  • persist(entity): 주어진 엔티티를 데이터베이스에 저장한다.
  • merge(entity): 주어진 엔티티를 데이터베이스에 저장하거나 업데이트한다.
  • find(entityClass, primaryKey): 주어진 엔티티 클래스와 기본 키를 기반으로 엔티티를 조회한다.
  • remove(entity): 주어진 엔티티를 데이터베이스에서 삭제한다.
  • createQuery(query, resultClass): 주어진 JPQL(Java Persistence Query Language) 쿼리를 실행하여 결과를 조회한다.

쿼리 실행 시점

보통 Spring Data JPA에서는 JpaRepository 인터페이스를 사용한다.

public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);
}

이렇게만 작성해도

  • findByEmail() → 자동으로 SELECT * FROM users WHERE email = ? 쿼리 생성
  • save() → INSERT 또는 UPDATE 자동 실행
  • delete() → DELETE 자동 실행

즉, SQL을 직접 쓰지 않아도 JPA가 알아서 만들어주는 구조이다.

profile
내 세상을 넓혀가는 중

0개의 댓글