user 테이블을 설계할 때 @PrePersist와 @PreUpdate 어노테이션을 사용했다.
@PrePersist와 @PreUpdate 은 Entity가 Persist(데이터베이스에 삽입) 또는 Update(데이터베이스에 수정)되기 전에 JPA Provider가 자동으로 실행해야 하는 메서드를 지정되는데 사용되는 JPA의 어노테이션이다.
@PrePersistEntity가 영속화(삽입)되기 직전에 실행되어야 하는 메서드 표시할 때 사용
주로 createdAt같은 필드를 자동으로 세팅할 때 사용한다.
@PreUpdate데이터베이스에서 Entity가 업데이트되기 직전에 실행되어야 하는 메서드 표시하는 데 사용
Entity의 변경사항이 데이터베이스와 동기화되기 전에 JPA Provider에 의해 자동으로 호출된다.
변경 시점을 자동으로 기록할 때 자주 사용된다.
JPA(Java Persistence API) 는
자바 객체(Object)를 데이터베이스 테이블에 자동으로 매핑해주는 ORM(Object-Relational Mapping) 기술 표준이다.
즉, SQL을 직접 쓰지 않고도 자바 객체로 DB 데이터를 다룰 수 있게 해주는 기술이다.
JPA를 사용하면 객체 지향 프로그래밍과 데이터베이스 간의 매핑을 간단하게 처리할 수 있다.
객체와 관계형 데이터베이스 간의 변환 작업을 자동으로 처리해주는 기술
개발자가 반복적인 SQL을 직접 작성하지 않아도 된다.
반면 SQL Mapper는 객체와 테이블 간 관계를 매핑하는 것이 아니다.
SQL문을 직접 작성하고 쿼리 수행한 결과를 어떤 객체에 매핑할지 바인딩하는 방법이다.
SQL Mapper에는 MyBatis 등이 있다.
JPA는 쿼리를 만들지 않아도 되고, 객체 중심의 개발이 가능하다. 하지만 복잡한 쿼리는 해결이 어렵다.
반면, MyBatis는 자바에서 SQL Mapper 지원하는 프레임워크다.
쿼리문을 xml로 분리하여 작성할 수 있고, 복잡한 쿼리문도 작성할 수 있다.
그러나 객체와 쿼리문 모두 관리해야 하고, CRUD 메소드를 직접 다 구현해야 한다.
| 구분 | JPA | MyBatis |
|---|---|---|
| 기본 개념 | ORM (객체 중심 매핑) | SQL Mapper (SQL 중심 매핑) |
| SQL 작성 여부 | 직접 작성할 필요 없음 (자동 생성) | 개발자가 SQL 직접 작성 |
| 코드량 | 적음 → 생산성 높음 | 많음 → 세밀한 제어 가능 |
| 유지보수성 | 엔티티 기반 → 필드명 변경 시 자동 반영 | SQL 수정 필요 (매핑 수동) |
| 성능 튜닝 | Hibernate 내부 최적화 활용 | SQL 직접 최적화 가능 |
| 복잡한 쿼리 처리 | JPQL, QueryDSL 등 사용 | 복잡한 SQL 자유롭게 작성 가능 |
| 대표 사용 예시 | Spring Data JPA, Hibernate | MyBatis, MyBatis-Spring |
| 학습 난이도 | ORM 개념 이해 필요 (초반 러닝커브 有) | SQL 위주 → 상대적으로 직관적 |
| 트랜잭션 관리 | Spring이 자동 관리 | 직접 제어 가능 |
| 적합한 경우 | 도메인 중심 설계(Domain-Driven Design) | DB 중심, 복잡한 SQL 중심 서비스 |
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와 직접 연결하지 않는 대신, 영속성 컨텍스트라는 가상의 메모리 공간을 만들어 엔티티 객체를 관리한다.
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차 캐시(영속성 컨텍스트)에 저장된 스냅샷과 현재 엔티티 객체를 비교하여 변경 사항을 감지하기 때문이다.

Controller → Service → Repository (JPA)
↓
EntityManager / Hibernate
↓
Database (SQL)
JPA는 “표준”일 뿐이고, 실제 동작은 Hibernate라는 구현체가 맡는다.
실행 로그에 Hibernate: insert into ... 같은 쿼리가 찍히는 이유도 이 때문이다
위에서 언급한 EntityManager는 Hibernate가 제공하는 핵심 API이며 Spring Data 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가 알아서 만들어주는 구조이다.