[TIL] CRUD Code Shadowing 1회차
JPA Entity, Lombok, Repository를 사용해 도메인 모델을 구성하는 과정을 정리한 글입니다.
For the English version of this post, see here.
[TIL] CRUD Code Shadowing 1회차
Entity
@Entity 어노테이션
- 이 클래스는 DB 테이블이랑 매핑되는 클래스 라는 것을 알려줌
- JPA 의존성을 추가해줘야함 -> JPA를 통해 이 클래스를 테이블로 인식함
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'(build.gradle) - JPA가 이 객체를 보고 INSERT / UPDATE / SELECT 다 해줌
@Getter 어노테이션
- 모든 필드에 대한 getter 메서드 자동 생성
- Lombok 라이브러리를 통해 사용 가능
@Table(name = "products")
- 테이블 이름을 직접 지정
- default로 클래스 이름 따라가지만, 위 어노테이션을 통해 DB 테이블 이름을 강제로 지정할 수 있음
@NoArgsConstructor(access = AccessLevel.PROTECTED)
- 기본 생성자를 만들어주는데, 접근 제한을 둠
- JPA는 객체 만들 때 기본 생성자 필요한데, 아무나 new Product() 못하게 막고 싶기 때문에 protected 사용함
- JPA는 사용 가능하고, 외부에서는 마음대로 생성 못함 -> JPA 전용 생성자 느낌
- Lombok 라이브러리를 통해 사용 가능
기본 생성자가 왜 필요한가
- JPA가 객체를 ‘리플렉션’으로 만들기 때문에 무조건 필요함
- JPA 동작방식
1 2 3
Product p = Product.class.newInstance(); // 기본 생성자 호출 p.setName(...); p.setPrice(...);
- DB에서 데이터 꺼낼 때 위 코드 같은 느낌
- JPA는 new Product(name, price) 이렇게 생성자를 쓰는 게 아니라,
- 빈 객체를 먼저 만들고 -> 값을 나중에 넣음 -> 그렇기 때문에, 기본 생성자 필수, public or protected만 가능
- 기본 생성자는 빈 객체를 만들 때 호출되는 통로
- 기본 생성자로 빈 객체 생성 -> 필드에 값 채움
- 프레임워크 입장에서는 클래스 구조를 다 알 수 없으니까 기본 생성자로 만들고 반사(reflection) 같은 걸 써서 채우는 방식
@Id: PK(Primary Key) 지정, 테이블의 대표 값
@GeneratedValue: ID 자동 생성
- IDENTITY 전략(
strategy = GenerationType.IDENTITY)- DB가 자동으로 증가시킴 (auto increment)
- insert 할 때 id 안 넣어도 됨
- DB가 알아서 넣어줌
생성자
1
2
3
4
public Product(String name, Integer price) {
this.name = name;
this.price = price;
}
- 우리가 직접 쓰는 생성자 생성
- 기본 생성자는 JPA용, 위 생성자는 비즈니스용 ex)
new Product("콜라", 2000);: 의미 있는 생성
update 메서드
1
2
3
4
public void update(String name, Integer price) {
this.name = name;
this.price = price;
}
- 왜 setter 안 사용하고 위 코드처럼 사용하는지
- JPA에서는 setter를 남발하면 위험함
- setter 문제: 중간 상태 깨질 수 있고, 로직 없이 값만 바뀜 (필드 하나씩 수정하기 때문)
- JPA에서는 setter를 남발하면 위험함
- update 메서드 장점: 한 번에 변경하며, 나중에 검증 로직을 추가할 수 있음
DTO
- DTO는 클라이언트 -> 서버로 들어오는 데이터를 받는 용도
1. RequestDto
@Getter
- getter 자동 생성 -> controller / service에서 값을 꺼내야 하기 때문에 사용
@NoArgsConstructor
- 기본 생성자 자동 생성 -> Spring이 JSON에서 객체로 변환할 때 필요
- 스프링도 JPA처럼 ‘빈 객체 먼저 만들고 값 넣는 방식’ 사용
@NotBlank
- String 전용
- null도 안되고, “” 빈 문자열도 안되고, 공백 “ “도 안될 때
@NotNull
- null만 금지
- 0은 허용됨
@Min
- 최소값 제한 ex)
@Min(0): 가격은 0 이상이어야 한다는 뜻
jakarta.validation.constraints
- 유효성 검사 어노테이션 패키지
@NotBlank,@NotNull,@Min등을 사용하기 위해 import 해야함implementation 'org.springframework.boot:spring-boot-starter-validation'build.gradle에 추가
DTO(Data Transfer Object)와 Entity의 차이
- DTO: 데이터를 옮기기 위한 객체
- 요청 데이터 받기, 응답 데이터 보내기, 계층 간 데이터 전달
- 상품을 저장하는 진짜 객체라기보다, 요청으로 들어온 값 묶음
- Entity: DB 테이블과 직접 매핑되는 객체
- JPA가 DB의 테이블과 연결해서 관리
-> DTO는 바깥세상용, Entity는 내부 DB 관리용
2. ResponseDto
@Getter
- getter 자동 생성 -> 응답 데이터 꺼낼 때 필요
필드에 final 붙는 이유
- 한 번 값 넣으면 절대 못 바꿈 -> 응답 데이터는 수정 못 하게 잠그기 위함
생성자
1
2
3
4
5
public ProductResponse(Product product) {
this.id = product.getId();
this.name = product.getName();
this.price = product.getPrice();
}
- Entity -> DTO 변환
- 동작 흐름
- DB에서 가져온 엔티티의 내부(Entity의 Product product)에서
product.getId(),product.getName(),product.getPrice()꺼내서 DTO에 넣음
- DB에서 가져온 엔티티의 내부(Entity의 Product product)에서
- Entity를 그대로 반환하면 안되기 때문
- Entity를 그대로 반환하면,
- DB 구조 그대로 노출됨
- 필요 없는 필드도 다 나감
- 보안 위험 있음
- 구조 바꾸기 어려움
- DTO로 변환하면
- 필요한 값만 골라서 보냄
- API 구조 자유롭게 설계 가능
- Entity 보호됨
- Entity를 그대로 반환하면,
requestDto와 responseDto의 차이
- RequestDto
- 클라이언트 -> 서버
- 입력용
- validation 있음 ex)
@NotNull - 아직 DB랑 관계 없음
- ResponseDto
- 서버 -> 클라이언트
- 출력용
- 가공된 데이터
- Entity 기반