JPA(Java Persistence API)는 자바에서 ORM(Object-Relational Mapping)을 표준화한 인터페이스입니다.
쉽게 말해, JPA는 자바 객체와 데이터베이스 테이블을 자동으로 매핑해주는 기술입니다.
JPA 자체는 인터페이스이며, 실제 구현체로는 대표적으로 Hibernate가 있습니다. Hibernate는 사실상의 표준으로 Spring Boot에서 사용하는 JPA는 대부분 Hibernate 기반입니다.
ORM은 객체와 관계형 데이터베이스의 데이터를 자동으로 매핑해주는 기술입니다.
String sql = "SELECT id, name, email FROM users WHERE id = ?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setLong(1, userId);
ResultSet rs = pstmt.executeQuery();
User user = null;
if (rs.next()) {
user = new User();
user.setId(rs.getLong("id"));
user.setName(rs.getString("name"));
user.setEmail(rs.getString("email"));
}
// 같은 기능을 JPA로 구현
User user = entityManager.find(User.class, userId);
기존 JDBC 방식은 다음과 같은 문제점이 있었습니다.
JPA는 이러한 반복적이고 불편한 작업을 대신 처리해 줍니다.
| 항목 | 설명 |
|---|---|
| 생산성 | SQL 작성 줄어듦, 반복 코드 감소 |
| 유지보수성 | 엔티티 클래스 위주로 개발 → 변경 추적 쉬움 |
| 객체지향적 개발 | 객체 중심의 도메인 설계 가능 |
| 트랜잭션 처리 | EntityManager가 내부적으로 처리 지원 |
| 캐싱 | 1차 캐시(Persistence Context)로 성능 최적화 |
public void createUser(User user) {
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = dataSource.getConnection();
conn.setAutoCommit(false); // 트랜잭션 시작
String sql = "INSERT INTO users (name, email, created_at) VALUES (?, ?, ?)";
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, user.getName());
pstmt.setString(2, user.getEmail());
pstmt.setTimestamp(3, new Timestamp(System.currentTimeMillis()));
pstmt.executeUpdate();
conn.commit(); // 트랜잭션 커밋
} catch (SQLException e) {
if (conn != null) {
try {
conn.rollback(); // 롤백
} catch (SQLException ex) {
// 로그 처리
}
}
throw new RuntimeException(e);
} finally {
// 자원 정리
if (pstmt != null) try { pstmt.close(); } catch (SQLException e) {}
if (conn != null) try { conn.close(); } catch (SQLException e) {}
}
}
@Transactional
public void createUser(User user) {
entityManager.persist(user);
}
| 항목 | 설명 | 실제 효과 |
|---|---|---|
| 생산성 | SQL 작성 줄어듦, 반복 코드 감소 | 개발 시간 단축 |
| 유지보수성 | 엔티티 클래스 위주로 개발 → 변경 추적 쉬움 | 스키마 변경 시 자동 반영 |
| 객체지향적 개발 | 객체 중심의 도메인 설계 가능 | 비즈니스 로직에 집중 |
| 트랜잭션 처리 | EntityManager가 내부적으로 처리 지원 | 트랜잭션 관리 자동화 |
| 캐싱 | 1차 캐시(Persistence Context)로 성능 최적화 | 동일 엔티티 중복 조회 방지 |
| 지연 로딩 | 필요할 때만 연관 데이터 로딩 | 메모리 사용량 최적화 |
엔티티는 데이터베이스 테이블과 매핑되는 자바 객체입니다.
@Entity
@Table(name = "users") // 테이블 이름 지정 (생략 시 클래스명 사용)
public class User {
@Id // 기본키 지정 (*필수)
@GeneratedValue(strategy = GenerationType.IDENTITY) // 자동 증가
private Long id;
@Column(name = "user_name", nullable = false, length = 50) // 컬럼 매핑
private String name;
@Column(unique = true) // 유니크 제약조건
private String email;
@Temporal(TemporalType.TIMESTAMP) // 날짜/시간 타입 지정
@Column(name = "created_at)
private Date createdAt;
// 기본 생성자 필수 (JPA 요구사항)
public User() {}
// 생성자, getter, setter ...
}
@Entity와 @Id는 필수 애노테이션이며, 컬럼은 @Column으로 세부 설정 가능합니다.영속성 컨텍스트는 엔티티를 관리하는 환경입니다. 1차 캐시 역할을 하며, 동일한 엔티티는 한 번만 DB에서 조회합니다.
// 동일한 ID로 두 번 조회해도 SQL은 한 번만 실행됨 (캐싱)
user user1 = entityManager.find(User.class, 1L); // SQL 실행
user user2 = entityManager.find(User.class, 1L); // 캐시에서 반환
System.out.println(user1 == user2); // true (동일한 객체)
엔티티는 JPA 기준으로 비영속 → 영속 → 준영속 → 삭제 상태를 가집니다.
// 1. 비영속 (new/transient) - 새로운 객체, JPA와 관계없음
User user = new User();
user.setName("홍길동");
// 2. 영속 (managed) - 영속성 컨텍스트에 관리됨
entityManager.persist(user);
// 3. 준영속 (detached) - 영속성 컨텍스트에서 분리됨
entityManager.detach(user);
// 4. 삭제 (removed) - 삭제 예정 상태
entityManager.remove(user);
persist(), detach(), remove() 같은 메서드로 조작됩니다.객체 간의 관계를 DB 외래 키처럼 표현하는 것이 연관관계 매핑입니다.
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "team", cascade = CascadeType.ALL)
private List<User> users = new ArrayList<>();
}
@Entity
public class user {
@id @GeneratedValue
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY) // 지연 로딩
@JoinColumn(name = "team_id")
private Team team;
}
@ManyToOne, @OneToMany, @JoinColumn 등을 이용해 관계를 설정합니다.JPA는 객체 관계를 언제 로딩할지 전략을 설정할 수 있습니다.
// 지연 로딩 (LAZY) - 실제 사용할 때 조회
User user = entityManager.find(User.class, 1L);
System.out.println(user.getName()); // User만 조회
System.out.println(user.getTeam().getName()); // 이때 Team 조회
// 즉시 로딩 (EAGER) - 처음부터 함께 조회
// @ManyToOne(fetch = FetchType.EAGER)
JPA의 persist(), detach(), remove(), merger()는 모두 EntityManager가 제공하는 엔티티 상태 전환용 메서드입니다.
각 메서드는 엔티티의 생명주기 상태를 바꾸는 역할을 하며, JPA 동작의 핵심 중 하나입니다.
INSERT SQL이 실행됨User user = new User();
user.setName("홍길동");
entityManager.persist(user); // 이제부터 영속 상태
User user = entityManager.find(User.class, 1L); // 영속 상태
entityManager.detach(user); // 더 이상 관리되지 않음
DELETE SQL이 실행됨User user = entityManager.find(User.class, 1L);
entityManager.remove(user); // 삭제 예약 (DB에서 제거됨)
UPDATE SQL이 실행됨User detachedUser = new User();
detachedUser.setId(1L); // 기존에 DB에 있던 ID
detachedUser.setName("업데이트된 이름");
// 병합: 새로운 영속 객체를 반환
User mergedUser = entityManager.merge(detachedUser);
- merge()는 원본 객체(detachedUser)를 그대로 영속화하지 않고, 복사한 새로운 객체(mergedUser)를 만들어 영속화합니다.
- 따라서 이후 작업은 반드시 mergedUser를 사용해야 합니다.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpatest");
persistence.xml 또는 application.yml)을 기반으로 전체 애플리케이션에서 공통으로 사용할 팩토리 객체 생성EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin(); // 트랜잭션 시작
persist() 등이 작동함em.persist(new User("kim")); // 영속화
tx.commit(); // DB 반영
// JPA 실행 시 다음과 같은 SQL이 자동 생성됨
Hibernate:
insert
into
users
(created_at, email, user_name)
values
(?, ?, ?)
@Transactional 사용Spring Boot 환경에서는 @Transactional 애노테이션으로 트랜잭션을 자동으로 시작하고 종료할 수 있습니다.
직접 begin()/commit()을 호출하지 않아도 되며, 메서드 단위로 트랜잭션 범위를 지정할 수 있습니다. 메서드가 시작될 때 트랜잭션을 열고, 정상 종료되면 자동으로 커밋, 예외 발생 시 롤백됩니다.
@Service
public class UserService {
@PersistenceContext
private EntityManager entityManager;
@Transactional
public void saveUser(User user) {
entityManager.persist(user); // 트랜잭션 자동 시작
// 예외가 없으면 자동 커밋됨
}
}
persist()와 같은 JPA 명령이 트랜잭션 내에서 수행됨commit() 호출RuntimeException 또는 Error가 발생하면 자동으로 rollback() 처리@Transactional은 public 메서드에서만 제대로 동작합니다 (Spring AOP의 제약).RuntimeException에만 rollback되며, 체크 예외(예: IOException 등)는 rollback되지 않습니다.