자바 백엔드 입문 45편 — @Entity Repository JPA 두 축

2026-05-16자바 백엔드 입문

자바 백엔드 입문 45편. @Entity 클래스 정의와 JpaRepository 인터페이스 한 줄로 자동 CRUD를 만드는 JPA 두 축. 회사 직원 명단 비유로 풀어쓴 학습 노트.

📚 자바 백엔드 입문 · 45편 — @Entity Repository JPA 두 축

이 글은 자바 백엔드 입문 시리즈 59편 중 45편이에요. 44편에서 JPA·Hibernate·Spring Data JPA 세 이름의 관계를 잡았다면, 이번 45편은 실제 코드의 두 축 — @Entity 클래스 + JpaRepository 인터페이스 를 풀어 가요. JPA 코드의 90%가 이 두 가지의 조합.

두 축이 헷갈리는 이유

JPA를 처음 배우면 — "DB 테이블이 자바 클래스가 됐다" 같은 표현이 나와요. 어떻게 자바 클래스가 테이블이 되는지가 안 잡혀요. 또 JpaRepository 인터페이스 한 줄만 정의했는데 — 메서드 16개가 자동으로 생긴다는 것도 마법처럼 느껴져요.

이 글에서는 회사 직원 명단 비유로 풀어요. Entity = "직원 카드(이름·사번·부서가 박힌 종이)", Repository = "인사팀에 "김 과장 데려와" 한 마디 하면 알아서 처리". 끝까지 따라오시면 두 축이 한 그림에 들어와요.

@Entity — 자바 클래스를 DB 테이블로

@Entity"이 자바 클래스를 DB 테이블에 매핑해 줘" 선언. 첫 Entity 예제.

import jakarta.persistence.*;
import java.time.LocalDateTime;
import lombok.*;

@Entity
@Table(name = "orders")
@Getter @Setter @NoArgsConstructor
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "product_id", nullable = false)
    private Long productId;

    @Column(nullable = false)
    private int amount;

    @Column(length = 20, nullable = false)
    private String status;

    @Column(name = "created_at")
    private LocalDateTime createdAt;
}

이 한 클래스가 "orders 테이블의 행 한 개" 에 대응해요. 5개 필드 = 5개 컬럼. 클래스 인스턴스 하나 = 행 하나.

5개 핵심 매핑 어노테이션

위 예제에 박힌 어노테이션 풀이.

@Entity — 필수

"이 클래스는 JPA 관리 대상" 선언. JPA가 시작 시 이 어노테이션 박힌 클래스를 다 스캔해 DB 매핑 메타데이터 생성.

@Table — 테이블 이름

@Table(name = "orders")

자바 클래스명(Order)과 DB 테이블명(orders)이 다를 때 명시. 같으면 생략 가능. 한국 회사 컨벤션은 보통 클래스 = PascalCase(Order), 테이블 = snake_case(orders) — 그래서 @Table 거의 항상 박혀요.

@Id — 기본 키

@Id
private Long id;

"이 필드가 PK" 선언. 모든 Entity는 @Id 박힌 필드 한 개가 필수. 안 박으면 시작 시 예외.

@GeneratedValue — PK 자동 생성

@GeneratedValue(strategy = GenerationType.IDENTITY)

"PK 값을 DB가 자동 생성해 줘" (AUTO_INCREMENT). 전략 4가지:

전략 DB
IDENTITY MySQL·PostgreSQL의 AUTO_INCREMENT
SEQUENCE Oracle·PostgreSQL의 시퀀스
TABLE 별도 키 생성 테이블 (드물게)
AUTO DB가 알아서 (Hibernate 기본)

신규 프로젝트 = IDENTITY 또는 SEQUENCE 둘 중 하나가 거의 표준.

@Column — 컬럼 세부 설정

@Column(name = "product_id", nullable = false, length = 20)
private String status;
옵션 의미
name 컬럼 이름 (필드명과 다를 때)
nullable NOT NULL 여부 (기본 true = NULL 허용)
length 문자열 길이 (기본 255)
unique UNIQUE 제약
updatable UPDATE 시 포함 여부

JPA가 이 정보로 DDL을 자동 생성할 수 있어요. 다만 운영에서는 44편ddl-auto: validate 가 표준이라 — DDL은 Flyway 같은 도구로 관리, JPA는 "검증 일치" 만 확인.

Entity 클래스 필수 룰 3가지

JPA Entity가 동작하려면 다음 3가지 룰을 지켜야 해요.

  1. @Entity 박기 — 필수
  2. 기본 생성자 (@NoArgsConstructor) — JPA가 리플렉션으로 객체 생성, 기본 생성자 필요
  3. @Id 필드 한 개 이상 — PK 없으면 동작 X

Lombok @Getter @Setter @NoArgsConstructor 박으면 1·2번 자동 처리. 자주 만나는 표준 패턴.

JpaRepository — Repository의 첫 줄

이제 두 번째 축. JpaRepository 인터페이스 한 줄 정의.

public interface OrderRepository extends JpaRepository<Order, Long> {
}

이게 끝. 인터페이스 한 줄. 구현 클래스 안 만들어도 — Spring Data JPA가 시작 시 자동으로 구현체 생성 + Bean 등록.

@Service
@RequiredArgsConstructor
public class OrderService {

    private final OrderRepository orderRepo;

    public Order findById(Long id) {
        return orderRepo.findById(id).orElseThrow();
    }

    public Order save(Order order) {
        return orderRepo.save(order);
    }

    public void delete(Long id) {
        orderRepo.deleteById(id);
    }
}

findById·save·deleteById 같은 메서드가 어디서 왔나? — JpaRepository 인터페이스에 미리 정의된 16개 기본 메서드. 우리가 정의 안 해도 자동 동작.

JpaRepository 기본 메서드 16종

자주 만나는 기본 메서드.

메서드역할
save(entity)INSERT 또는 UPDATE
saveAll(list)여러 개 한 번에
findById(id)Optional<T> 반환
findAll()모든 행 조회
findAllById(ids)여러 PK로 조회
count()전체 개수
existsById(id)존재 여부
deleteById(id)PK로 삭제
delete(entity)객체로 삭제
deleteAll()모두 삭제 (위험)
findAll(Sort)정렬 조건
findAll(Pageable)페이징

위 12개 + 보조 4개 = 인터페이스 한 줄로 16개 메서드가 자동 동작. "이게 마법인가?" 가 처음 봤을 때 든 반응의 정체.

save가 INSERT인지 UPDATE인지

save() 동작이 자주 헷갈리는 부분. @Id 필드가 null이면 INSERT, 값이 있으면 UPDATE.

Order newOrder = new Order(...);   // id = null
orderRepo.save(newOrder);          // INSERT, id 자동 부여

Order existing = orderRepo.findById(123L).get();   // id = 123
existing.setAmount(20000);
orderRepo.save(existing);          // UPDATE

다만 — 이미 영속성 컨텍스트에 있는 객체는 save() 없이도 변경 자동 반영. 32편에서 깊이 다룰 핵심 개념 ("변경 감지" 또는 "Dirty Checking").

Repository 위치 — 패키지·Bean 등록

JpaRepository 인터페이스는 컴포넌트 스캔 범위 안의 패키지에 두면 자동 인식. @Repository 어노테이션 안 박아도 OK — Spring Data JPA가 "JpaRepository 상속한 인터페이스는 자동 Bean" 처리해줘요.

다만 코드 가독성 위해 박는 회사도 있어요.

@Repository                            // 옵션 (안 박아도 동작)
public interface OrderRepository extends JpaRepository<Order, Long> {}

41편@Repository 보너스(예외 변환)도 동일하게 동작.

Entity는 Service·Controller에서 직접 다루지 말 것 — DTO 패턴

28편 @RestController 에서 다뤘던 룰 다시 강조. Entity를 컨트롤러 응답으로 직접 반환하지 마세요. 두 가지 함정.

  1. LazyLoading 함정 — 트랜잭션 닫힌 후 Jackson이 getter 호출 시 LazyInitializationException
  2. DB 모델 노출 — 비밀번호·내부 필드까지 API에 그대로 노출

표준 패턴 = Entity ↔ DTO 분리.

// Entity (DB 전용)
@Entity
public class User {
    @Id private Long id;
    private String email;
    private String password;        // 절대 응답에 X
    private LocalDateTime createdAt;
}

// Response DTO (API 응답 전용)
@Getter @AllArgsConstructor
public class UserResponse {
    private Long id;
    private String email;
    private LocalDateTime createdAt;
    // password 빠짐

    public static UserResponse from(User user) {
        return new UserResponse(user.getId(), user.getEmail(), user.getCreatedAt());
    }
}

// Controller
@GetMapping("/{id}")
public UserResponse get(@PathVariable Long id) {
    return UserResponse.from(userService.findById(id));
}

코드 양이 늘어 보이지만 — 한국 회사 백엔드의 거의 표준 패턴.

Entity 동등성 (equals·hashCode)

Entity의 equals()·hashCode() 처리는 "PK만 비교" 가 표준.

@Entity
@EqualsAndHashCode(of = "id")           // Lombok — id 필드만으로 비교
public class Order {
    @Id private Long id;
    // ...
}

Lombok 기본 @Data 박으면 모든 필드를 비교 — JPA에서는 위험. Set·Map에서 Entity를 키로 쓸 때, "PK가 같으면 같은 Entity" 로 처리해야 일관성 유지.

🎯 첫 JPA 코드 — 5분 시작 골격

1️⃣ Entity 클래스 (@Entity·@Id·@GeneratedValue·@Column) 2️⃣ Repository 인터페이스 (extends JpaRepository<Entity, ID>) 3️⃣ Service에 Repository 주입 + save·findById 호출. 이 3단계로 첫 JPA 코드 완성.

한 줄 정리 — JPA 두 축 = @Entity 클래스 + JpaRepository 인터페이스. Entity는 "테이블에 매핑된 자바 객체", Repository 한 줄로 16개 기본 CRUD 자동. 컨트롤러에는 Entity 직접 X, DTO 변환 표준.

시험 직전 한 번 더 — Entity·Repository 입문자가 매번 헷갈리는 것

  • @Entity = 자바 클래스를 DB 테이블에 매핑
  • 필수 = @Entity + 기본 생성자 + @Id 필드
  • @Table(name = "orders") = DB 테이블 이름 명시
  • 클래스 PascalCase ↔ 테이블 snake_case 한국 컨벤션
  • @Id = PK 필드 표시 (필수)
  • @GeneratedValue = PK 자동 생성 전략
  • 4가지 전략 = IDENTITY·SEQUENCE·TABLE·AUTO
  • MySQL·PostgreSQL 신규 프로젝트 = IDENTITY 또는 SEQUENCE
  • @Column = 컬럼 세부 설정 (name·nullable·length·unique·updatable)
  • DDL은 ddl-auto: validate 운영 표준 (Flyway·Liquibase 별도)
  • Lombok 표준 = @Getter @Setter @NoArgsConstructor
  • JpaRepository<Entity, ID> 한 줄 = 16개 CRUD 자동
  • 인터페이스만 정의 — 구현체는 Spring이 자동 생성
  • 기본 메서드 = save·findById·findAll·count·existsById·deleteById...
  • save() = id null이면 INSERT, 값 있으면 UPDATE
  • @Repository 어노테이션 = 옵션 (안 박아도 동작)
  • JpaRepository 인터페이스 = 컴포넌트 스캔 범위에 두기
  • Entity 직접 컨트롤러 반환 X — DTO 패턴 표준
  • DTO 분리 이유 = LazyLoading 함정 + DB 모델 노출 방지
  • UserResponse.from(entity) 정적 메서드 변환 패턴
  • @EqualsAndHashCode(of = "id") = PK만 비교 표준

시리즈 다른 편 (앞뒤 글 모음)

이전 글:

다음 글:

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!