JPA- 4월 10일

lavender_je
|2025. 4. 10. 18:04
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 클래스에 대한 테스트는 일반적으로 다음을 검증하게 될 거야:

  1. save()로 저장했을 때 DB에 잘 들어갔는가?
  2. findById()findByItemCode()로 올바르게 조회가 되는가?
  3. 존재하지 않는 아이템을 조회했을 때 Optional.empty()가 나오는가?
  4. 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