자바 백엔드 입문 45편. @Entity 클래스 정의와 JpaRepository 인터페이스 한 줄로 자동 CRUD를 만드는 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가지 룰을 지켜야 해요.
@Entity박기 — 필수- 기본 생성자 (
@NoArgsConstructor) — JPA가 리플렉션으로 객체 생성, 기본 생성자 필요 @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를 컨트롤러 응답으로 직접 반환하지 마세요. 두 가지 함정.
- LazyLoading 함정 — 트랜잭션 닫힌 후 Jackson이 getter 호출 시
LazyInitializationException - 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" 로 처리해야 일관성 유지.
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만 비교 표준
시리즈 다른 편 (앞뒤 글 모음)
이전 글:
- 40편 — Spring WebClient RestClient HTTP 클라이언트
- 41편 — JDBC와 DataSource
- 42편 — JdbcTemplate으로 SQL 다루기
- 43편 — @Transactional의 원리
- 44편 — JPA Hibernate Spring Data JPA 셋의 관계
다음 글: