Post

05 JPA 흐름

05 JPA 흐름

1. Note

  • 엔티티의 생명주기(생성·조회·삭제 등)는 Repository가 책임
    • DB와의 직접적인 연결, 영속성 컨텍스트 등록/삭제 등
  • 엔티티의 상태 변화(필드 변경, 연관관계 조작 등)는 엔티티 자체가 책임
    • 이미 영속 상태인 객체를 조작하면 트랜잭션 종료 시점에 자동으로 DB에 반영됨
  • FK를 통한 연관 데이터 접근도 마찬가지
    • 처음부터 Repository에서 조건을 걸어 조회할 수도 있지만
    • 이미 영속성 컨텍스트에 적재된 엔티티에서 접근하는 것이 효율적임 (객체 그래프 탐색)

2. Product

1. Product Entity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  @Entity
  @Table(name = "products")
  @Getter
  @NoArgsConstructor(access = AccessLevel.PROTECTED)
  @AllArgsConstructor
  @Builder
  public class Product {
  
      @Id
      @GeneratedValue(strategy = GenerationType.IDENTITY)
      private Long id;
  
      private String name;
  
      private int price;
  
      private int stock;
  
      // 비즈니스 로직
      // 엔티티 내부를 수정할 때 사용하는 것
      public void update(String name, int price, int stock) {
          this.name = name;
          this.price = price;
          this.stock = stock;
      }
  }

2. Product Repository

1
2
  // 레파지토리에 대한 접근과 관련된 부분
 public interface ProductRepository extends JpaRepository<Product, Long> { } 

3. Product Service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
  @Service
  @RequiredArgsConstructor
  @Transactional
  public class ProductService {
      
      // 레파지토리
      private final ProductRepository productRepository;
  
      // 제품 추가
      public Long createProduct(String name, int price, int stock) {
          // 객체를 만들고
          Product product = Product.builder()
                  .name(name)
                  .price(price)
                  .stock(stock)
                  .build();
          // 레파지토리에 save
          // productRepository.save(product) 리턴값은 product 엔티티
          // 따라서 생성한 엔티티의 ID(PK값)
          return productRepository.save(product).getId();
      }
  
      @Transactional(readOnly = true)
      public Product getProduct(Long id) {
          return productRepository.findById(id)
                  .orElseThrow(() -> new IllegalArgumentException("상품 없음"));
      }
  
      public void updateProduct(Long id, String name, int price, int stock) {
          // 바로 위에 퍼블릭 메소드에서 레파지토리에 접근해서 엔티티를 얻음.
          // 레파지토리에서 얻은 엔티티는 영속성등록이 되어있는 상태,
          // 따라서 이 값을 수정하면 트랜잭션이 끝나면 자동으로 엔티티가 관리됨.
          Product product = getProduct(id);
          product.update(name, price, stock);
      }
  
      public void deleteProduct(Long id) {
          //생명주기와 관련된 부분은 레포지토리 직접 접근
          productRepository.deleteById(id);
      }
  }

3. Product - ProductOption

1. Product/ProductOption Entity

1. 구조

1
2
3
4
5
6
7
8
  products
   - id
   - name
  
  product_options
   - id
   - name
   - product_id (FK)

2. Product(부모)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  @Entity
  @Getter
  @NoArgsConstructor(access = AccessLevel.PROTECTED)
  public class Product {
  
      @Id @GeneratedValue
      private Long id;
  
      private String name;
      @OneToMany(mappedBy = "product", cascade = CascadeType.ALL)
      private List<ProductOption> options = new ArrayList<>();
       
      // 연관관계 편의 메서드 
      public void addOption(ProductOption option) {
          // 파라미터 옵션에 추가를 해서 엔티티매니저가 관리하게 된거고
          // 이 방법은 레포지토리매니저에서 엔티티에 접근하는게 아닌, 프로덕트를 통해서 접근
          options.add(option);  
          
          // 여기서 부모 엔티티 설정을 함.
          // 부모엔티티 설정을 하지 않으면, 그냥 옵션 테이블에 등록된 엔티티가됨.
          option.setProduct(this); 
      }
  }

3. ProductOption (자식)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  @Entity
  @Getter
  @NoArgsConstructor(access = AccessLevel.PROTECTED)
  public class ProductOption {
  
      @Id @GeneratedValue
      private Long id;
  
      private String name;
  
      @ManyToOne(fetch = FetchType.LAZY)
      @JoinColumn(name = "product_id")
      private Product product;
  
      public void setProduct(Product product) {
          this.product = product;
      }
  }

3. Product Service - Create() Method

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  @Transactional
  public void create() {
    
    // 새로운 객체를 만들고
    Product product = new Product("아이폰");

    //옵션에 2종류를 추가함.
    ProductOption op1 = new ProductOption("128GB");
    ProductOption op2 = new ProductOption("256GB");
    
    // 연관관계 메서드
    // 옵션을 2가지 추가함.
    product.addOption(op1);
    product.addOption(op2);
    
    // 레파지토리에 저장
    // Product 테이블에 “아이폰” 1건 저장
👉  // Product를 FK로 참조하는 ProductOption 2건이 추가
    productRepository.save(product);    
  }

4. Product Service - getProductOptions Method

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  @Transactional(readOnly = true)
  public void getProductOptions(Long productId) {
  
      // 상품 엔티티를 찾고
      Product product = productRepository.findById(productId).orElseThrow();
  
      // 연관관계로 옵션 조회
      List<ProductOption> options = product.getOptions();
  
      // 사용
      for (ProductOption option : options) {
          System.out.println(option.getName());
      }
  } 
This post is licensed under CC BY 4.0 by the author.