728x90
package io.shi.dao.global.entity;
import jakarta.persistence.*;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.Objects;
@Getter
//@Builder
@Entity
@Table(name = "items")
@NoArgsConstructor
public class Items {
@Id
@Column(name = "item_id")
@GeneratedValue(strategy = GenerationType.IDENTITY) //주키가 identity 타입으로 주었다
private Long id;
private String name;
private String itemCode;
@Setter
private Integer price;
private LocalDateTime createdAt;
@Builder
public Items(String name, String itemCode, Integer price) {
this.name = name;
this.itemCode = itemCode;
this.price = price;
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
Items items = (Items) o;
return Objects.equals(id, items.id) && Objects.equals(name, items.name) && Objects.equals(itemCode, items.itemCode) && Objects.equals(price, items.price) && Objects.equals(createdAt, items.createdAt);
}
@Override
public int hashCode() {
return Objects.hash(id, name, itemCode, price, createdAt);
}
}
🌱 이 클래스는 어떤 역할?
이 Items 클래스는 JPA에서 엔티티(Entity)로 사용될 DB 테이블과 매핑되는 클래스야.
즉, 이 클래스를 기반으로 JPA가 자동으로 items라는 테이블에 데이터를 저장하고 꺼내게 돼.
🔍 어노테이션 하나씩 해석
1. @Entity
@Entity
- 이 클래스가 JPA에서 관리하는 **엔티티(=DB 테이블로 매핑될 객체)**라는 의미야.
- JPA가 이 클래스를 보고:
👉 "아, 너는 테이블 하나랑 연결된 객체구나!" 라고 이해함. - 이 어노테이션 없으면 JPA가 이 클래스를 무시해.
2. @Table(name = "items")
@Table(name = "items")
- 실제 DB 테이블 이름을 items로 명시함.
- 만약 이걸 안 쓰면, 클래스 이름(Items)을 그대로 테이블 이름으로 쓴다고 생각해 (보통 소문자+언더스코어로 바꿔서 items라고 추정은 하지만).
- 명확히 이름을 지정해주는 습관이 좋음.
3. @Id
@Id
@Column(name = "item_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
✅ 역할:
- id 필드는 **이 테이블의 기본키(PK)**임을 의미해.
- 즉, 이 필드를 기준으로 데이터가 고유하게 식별돼.
🎯 @GeneratedValue – 여기가 제일 중요해!
@GeneratedValue(strategy = GenerationType.IDENTITY)
✔ 이게 하는 일:
👉 기본키(PK) 값을 자동으로 생성해주는 방법을 JPA에게 알려주는 거야.
즉, id를 내가 직접 넣지 않아도, DB가 알아서 값을 채워주는 방식을 의미해.
🔧 사용 가능한 전략들 (전략 = strategy)
@GeneratedValue(strategy = ...)
전략 종류 | 설명 | 어떤 DB에 적합? |
IDENTITY | DB에 insert 하면서 auto increment로 PK 생성 | MySQL, MariaDB |
SEQUENCE | DB에 sequence 객체를 만들어서, 다음 값을 가져옴 | Oracle, PostgreSQL |
TABLE | 별도의 테이블 만들어서 PK 관리 | 거의 안 씀 |
AUTO | JPA가 자동으로 DB에 맞는 전략 선택 | 자동 설정 |
💡 너가 쓴 IDENTITY 전략은?
@GeneratedValue(strategy = GenerationType.IDENTITY)
- DB에게 위임해서 자동 증가시키는 방식 (MySQL의 AUTO_INCREMENT랑 동일).
- insert 할 때, JPA가 SQL을 이렇게 날려:그리고 DB가 알아서 id를 채워줌.
insert into items (name, item_code, price, created_at) values (?, ?, ?, ?);
- 이 방식은 매우 직관적이지만, 단점이 하나 있음:
- id 값이 생성되어야만 insert가 완료되므로, JPA가 flush를 빨리 해버림. (→ 영속성 컨텍스트 특성 활용이 조금 제한됨)
4. @Column(name = "item_id")
- 이 필드가 DB의 item_id 컬럼과 매핑된다는 의미.
- 보통 PK에는 @Column 명시해주는 편이야 (명확하게 컬럼 이름 지정).
5. @NoArgsConstructor
@NoArgsConstructor
- JPA는 기본 생성자가 반드시 필요해!
- 내부에서 프록시 객체 만들거나 리플렉션으로 new 하는 상황이 많거든.
- 그래서 기본 생성자가 없으면 에러가 나.
6. @Builder
- 롬복에서 제공하는 빌더 패턴 생성기.
- 객체 생성 시 이렇게 쓸 수 있게 해줘:
Items item = Items.builder()
.name("apple")
.itemCode("ITEM_123")
.price(1000)
.build();
7. @Getter
- 모든 필드에 대해 getter를 자동으로 만들어주는 롬복 어노테이션.
- JPA는 필드에 직접 접근하지 않고 getter를 통해 값을 가져오는 경우가 많아, 필수임.
🧩 예시 흐름 정리
Items item = Items.builder()
.name("note")
.itemCode("ITEM_123")
.price(5000)
.build();
itemRepository.save(item);
- item.getId()는 null (id는 아직 없음)
- save()를 호출하면 JPA가 insert를 함.
- DB가 id를 auto increment로 생성해서 넣어줌.
- JPA는 insert 후에 DB가 넣어준 id를 다시 이 객체에 세팅해줌.
💬 요약
어노테이션 | 의미 |
@Entity | 이 클래스는 테이블과 연결된 엔티티야 |
@Table(name = "items") | 테이블 이름은 items야 |
@Id | 기본키 필드야 |
@GeneratedValue(strategy = IDENTITY) | DB가 자동으로 PK를 만들어줄 거야 (auto increment) |
@Column(name = "item_id") | DB에서 이 필드는 item_id 컬럼과 연결돼 |
@NoArgsConstructor | JPA용 기본 생성자 필요 |
@Builder, @Getter | 롬복 유틸리티 |
package io.shi.dao.global.entity;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Orders {
@Id
@Column(name = "order_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String orderCode;
private LocalDateTime orderedAt = LocalDateTime.now();
}
🌱 이 클래스는 어떤 역할?
이 Orders 클래스는 JPA에서 엔티티(Entity)로 사용될 DB 테이블과 매핑되는 클래스야.
@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Orders {
...
}
이 Orders 클래스는:
- JPA 엔티티
- 기본 생성자는 protected
- 주문 코드(orderCode)는 고유하고 필수
- 주문 시각(orderedAt)은 객체 생성 시 자동으로 현재 시간으로 설정
🔍 어노테이션 하나씩 해석
1. @Entity
@Entity
- JPA가 이 클래스를 테이블로 인식하게 해줌.
- 이 클래스의 인스턴스가 orders 테이블의 한 row가 되는 거야.
2. @Getter
@Getter
- 롬복에서 제공하는 어노테이션.
- 모든 필드에 대해 getId(), getOrderCode() 같은 getter 메서드 자동 생성.
3. @NoArgsConstructor(access = AccessLevel.PROTECTED)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
- 기본 생성자를 만들어주는데, protected 접근 제한자 붙임.
- 이유:
- JPA는 기본 생성자가 반드시 있어야 함 (프록시 생성 등 내부 작동용).
- 하지만 실수로 외부에서 막 생성하지 않게 protected로 제한함.
- ✅ 좋은 JPA 코드 스타일!
4. @Id + @GeneratedValue
@Id
@Column(name = "order_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
- id는 기본 키(PK) 역할
- @GeneratedValue(strategy = IDENTITY)는:
- DB가 AUTO_INCREMENT처럼 값을 자동 증가시키는 방식
- MySQL 같은 DB에서 주로 사용
- @Column(name = "order_id")는:
- DB에선 이 컬럼 이름이 order_id라는 걸 명시
5. @Column(unique = true, nullable = false)
@Column(unique = true, nullable = false)
private String orderCode;
- orderCode는 주문 식별용 코드
- 옵션 설명:
- unique = true: DB에서 중복 불가 (고유해야 함)
- nullable = false: null이 들어가면 에러남 (필수 컬럼)
즉, orderCode는 중복 없이 반드시 있어야 하는 값!
6. orderedAt
private LocalDateTime orderedAt = LocalDateTime.now();
- 주문 시각
- 별도의 어노테이션은 없지만, 필드 초기화로 객체 생성 시점의 현재 시간(LocalDateTime.now())으로 자동 설정됨.
- 이건 실제 DB에서 ordered_at 같은 컬럼으로 저장될 수 있어 (기본 설정 사용 시).
💡 요약 정리
필드명 | 역할 | 어노테이션 기능 요약 |
id | 주문의 기본 키 (PK) | @Id, @GeneratedValue로 자동 생성 |
orderCode | 주문 고유 식별자 | 고유 + 필수 값, 중복 불가 |
orderedAt | 주문 시간 | 생성 시 자동 시간 설정 (코드로) |
🚀 동작 예시
Orders order = new Orders("ORD_12345"); // 생성자가 추가되어 있다면
orderRepository.save(order);
- id는 비어 있음 → DB에서 자동으로 채움
- orderCode = ORD_12345 → 저장됨
- orderedAt = 현재 시간 → 저장됨
🔧 추가 팁
현재는 생성자가 없어서 orderCode를 외부에서 넣을 수 없지만, 일반적으로 다음처럼 생성자를 추가해 사용해:
@Builder
public Orders(String orderCode) {
this.orderCode = orderCode;
this.orderedAt = LocalDateTime.now();
}
이렇게 하면 빌더 패턴이나 생성자를 통해 객체 생성이 편해지고 명확해져.
package io.shi.dao.dao.hibernate;
import io.shi.dao.global.entity.Items;
import jakarta.persistence.EntityManager;
import jakarta.persistence.NoResultException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
@RequiredArgsConstructor
public class HibernateItemRepository {
private final EntityManager entityManager;
public Items save(Items items) {
entityManager.persist(items);
//entityManager.detach(items);
//entityManager.flush();
return items;
}
public Optional<Items> findById(Long id) {
Items items = entityManager.find(Items.class, id);
//select i.item_id as id ... from items i where i.item = ??
return Optional.ofNullable(items);
}
public Optional<Items> findByItemCode(String itemCode) {
//entityManager.find(Items.class, itemCode);
//JPQL
//Java Persistence Query Language
try{
String jpql = "SELECT i FROM Items i WHERE i.itemCode = :itemCode";
Items findItem = entityManager.createQuery(jpql, Items.class).setParameter("itemCode", itemCode).getSingleResult();
return Optional.of(findItem);
} catch (NoResultException e){
return Optional.empty();
}
}
public void updatePrice(String itemCode, Integer price) {
Optional<Items> itemOptional = findByItemCode(itemCode);
Items item = itemOptional.orElseThrow();
item.setPrice(price);
item.setPrice(price);
}
}
🌱 HibernateItemRepository 설명
이 클래스는 EntityManager를 이용해 Items 엔티티에 대한 데이터베이스 작업을 수행하는 Repository 클래스야.
@Repository
@RequiredArgsConstructor
public class HibernateItemRepository {
private final EntityManager entityManager;
- @Repository: Spring이 이 클래스를 데이터 접근 계층(DAO, Repository)으로 인식하게 해.
- @RequiredArgsConstructor: final 필드인 entityManager를 자동으로 주입 받게 해줘 (생성자 자동 생성).
✏️ save(Items items)
public Items save(Items items) {
entityManager.persist(items);
return items;
}
- persist()는 새로운 엔티티를 영속성 컨텍스트에 저장하고, 트랜잭션이 커밋되면 실제 DB에 반영돼.
- 반환값은 저장한 Items 객체.
🔸 주석 처리된 코드:
- detach(): 영속성 컨텍스트에서 해당 객체를 분리 (변경 감지 X)
- flush(): 영속성 컨텍스트의 변경사항을 즉시 DB에 반영 (커밋 전이라도 강제로 DB와 동기화)
✏️ findById(Long id)
public Optional<Items> findById(Long id) {
Items items = entityManager.find(Items.class, id);
return Optional.ofNullable(items);
}
- JPA의 기본 제공 메서드 find()를 사용해, 기본키로 조회해.
- 결과가 없을 수 있으므로 Optional로 감싸 반환.
✏️ findByItemCode(String itemCode)
public Optional<Items> findByItemCode(String itemCode) {
String jpql = "SELECT i FROM Items i WHERE i.itemCode = :itemCode";
Items findItem = entityManager.createQuery(jpql, Items.class)
.setParameter("itemCode", itemCode)
.getSingleResult();
return Optional.of(findItem);
}
- JPQL을 사용해서 itemCode로 조회하는 메서드야.
- getSingleResult()는 결과가 없으면 NoResultException을 던지기 때문에 try-catch로 감싸고, 없으면 Optional.empty()를 리턴.
✏️ updatePrice(String itemCode, Integer price)
public void updatePrice(String itemCode, Integer price) {
Optional<Items> itemOptional = findByItemCode(itemCode);
Items item = itemOptional.orElseThrow();
item.setPrice(price);
}
- 먼저 해당 아이템이 존재하는지 조회.
- 존재하면 가격을 변경 → 영속 상태이기 때문에 트랜잭션 커밋 시 DB에도 자동 반영돼!
(JPA의 변경 감지(dirty checking) 기능 덕분이야)
✅ 테스트 클래스의 역할과 메서드 설명
이 Repository 클래스에 대한 테스트는 일반적으로 다음을 검증하게 될 거야:
- save()로 저장했을 때 DB에 잘 들어갔는가?
- findById()와 findByItemCode()로 올바르게 조회가 되는가?
- 존재하지 않는 아이템을 조회했을 때 Optional.empty()가 나오는가?
- updatePrice()로 가격이 잘 바뀌는가?
728x90
'프로그래밍 > Spring' 카테고리의 다른 글
Hibernate실습, JPQL 활용 - 4월 14일 (0) | 2025.04.14 |
---|---|
Hibernate란 - 4월 11일 (0) | 2025.04.11 |
Mock란? - 4월 10일 (1) | 2025.04.10 |
Mock이란? - 4월9일 (0) | 2025.04.10 |
MyBatis란? (0) | 2025.04.09 |