[spring Boot]_13일차_리뷰, 주문, 리스트조회

youuu·2022년 11월 2일
0

SPRING

목록 보기
21/33

📌요약 :

🎯 enum :

항목나열.
▶ 편입시키기 위해선 @Enumerated(EnumType.STRING)사용.
Original로 쓰지말아라


🎯 JPA에서 FK 잡기:

@JoinColumn(name = "member_id")을 사용

  • 참조 무결성. 객체간에 참조하는 키는 FK로 잡아라.

🎯 막아놓을때 readOnly 를 사용:

@Transactional(readOnly = true)

❓ 왜 readOnly = true를 써야하나?
▶ 안 써도 된다. 하지만 사용하면 성능 최적화 및 실수로 데이터를 변경하는 일을 방지할 수도 있다.

  • DB에 따라 @Transactional(readOnly=true) 를 했을 때 만약 update 나 insert 연산이 수행된다고 하여도 오류가 날 수 있고 나지 않을 수도 있다.
  • @Transactional(readOnly=true) 를 하면 더티체킹을 건너뛰는등 여러 최적화 작업이 일어나기 때문에 성능이 향상된다.
  • 만약 DB가 master-slave 구조로 되어있다면 추가 설정을 통해서 CUD(create, update, delete) 는 master DB 로 가게 설정하고 Read 는 slave 로 가게 설정하여 workload를 분산할 수 있다.
    참고링크
    ❓ master-slave 구조
    🔺readOnly를 꼭 써야하는줄 알았다. 그런데 아래 글을 보고 생각이 달라졌다.
    readOnly 참고1
  • jpa를 사용하면서 우리가 해당 메서드에서 따로 설정을 하지 않는다면 select 메서드에 대해선 @Transactional(ReadOnly = true)가 나머지(save, update, delete)에 대해선 @Transactional이 자동으로 사용되어진다.
    readOnly 참고⭐⭐⭐

🎯 @Transactional ?

@Transactional을 사용하면 Spring AOP 가 해당 메소드 앞, 뒤에 transaction 시작과 transaction.commit() 을 실행해준다.
➡ SAVE하는것에 사용.(select 외에것.)


🎯 EGER ?

👨‍🏫say :
ManyToOne일때 ➡ LAZY. LAZY로 다 거는게 좋다. 기본 LAZY.
EAGER

  • EGER와 관련된 용어로 프록시, 지연로딩, 즉시로딩 이 있다.

1. 프록시

지연로딩을 사용하려면 실제 엔티티 객체 대신에 데이터베이스 조회를 지연할 수 있도록 지원하는 가짜 객체가 필요한데 이를 프록시라고 한다.

  • 프록시 클래스는 실제 클래스를 상속받아서 만들어지므로, 실제 클래스와 겉 모양이 유사하다.
  • 프록시 클래스는 Entity target 이라는 인스턴스를 가지고 있는데 이 필드는 실제 객체에 대한 참조를 보관하고 있다.
  • 프록시 객체의 메소드를 호출하면 프록시 객체의 메소드가 호출되는 것이 아닌, 참조하고 있는 객체의 메소드가 호출된다.
  • 프록시 객체는 아래와 같은 순서로 초기화가 되어 사용된다.

(1) 프록시 객체에 메소드를 호출하면, 초기화가 진행된다.
영속성 컨텍스트에 실제 엔티티가 생성되어 있지 않으면, 데이터베이스 접근하여 데이터를 가져와 실제 엔티티를 생성한다.
(2) 프록시 Entity target 인스턴스에 생성된 인스턴스의 참조를 할당한다.
(3) 프록시 객체는 실제 객체(즉, target의 참조변수)의 메소드를 호출한다.


  • 프록시 객체는 생성된 실제 엔티티를 상속받은 객체이므로 타입 체크 시에 주의해야 한다. 해당 객체가 프록시 객체인지, 실제 객체인지 확인을 하려면 이름을 보면 알 수 있다. obj.getClass().getName()을 했을 때, 객체의 이름에 ..javassist.. 가 있다면 이 객체는 프록시 객체이다!
    • PersistenceUnitUtil.isLoaded(Object entity) 메소드를 사용하면 프록시 인스턴스의 초기화 여부 확인 가능
  1. 즉시로딩, 지연로딩
    즉시로딩(EAGER)은 엔티티를 조회할 때, 연관된 엔티티도 함께 조회한다. (Question을 조회할 때, List 도 조회)

지연로딩(LAZY)은 연관된 엔티티를 실제 사용할 때 조회한다. (Quesion을 조회할 때, List도 사용한다면 그때만 조회)

프록시, 지연로딩, 즉시로딩 참고

🎯 Eager전략을 지양하는 이유 ?

  • 즉시 로딩을 사용할 경우 전혀 예상하지 못한 SQL이 나타날 확률이 높다.
  • join이 여러번 걸리게 되는 경우 성능상 문제가 발생할 수 있다.
  • 즉시로딩은 JPQL에서 N+1문제의 원인이 된다.
    참고

⭐⭐⭐

🎯💬 정리 :

  • ManyToOne, OneToOne ▶ 즉시로딩
  • OneToMany, ManyToMany ▶ 지연로딩 을 사용!
  • 항상 지연 로딩을 사용함으로써 성능 이슈가 발생할 가능성을 줄여주는 것이 좋다.
  • 실무에서 Eager전략의 사용은 지양

왜 LAZY 로딩을 써야할까?

@ManyToOne(fetch = FetchType.LAZY)


🎯 JPA 엔티티 작성할때 Setter 금지❗

👨‍🏫이 알려주신건 계속 setter를 써왔다. 그러다가 어제 생성자 접근 제어 방식으로 사용하였다.
😬 다른사람이 알려준 정보❗

  1. setter의 남용은 여기저기서 객체(엔티티)의 값을 변경할 수 있으므로 객체의 일관성을 보장할 수 없다.
  2. setter는 그 의도를 알기 힘들다.
  • 객체의 일관성을 유지하기 위해 객체 생성 시점에 값들을 넣어줌으로써 Setter 사용을 줄일 수 있다.

Setter 금지 참고

  • member_id라는 컬럼에 시퀸스를 주기 위해서
    @GeneratedValue( strategy = GenerationType.SEQUENCE, generator = "member3_seq_gen" )
	@GeneratedValue(
			        strategy = GenerationType.SEQUENCE,
			        generator = "member3_seq_gen"
			)
	
	@Column(name = "member_id")

  • 조인할 컬럼 : @JoinColumn(name = "delivery_id")

  • 맵핑을 당한것이다 ➡ mappedBy
    @OneToMany(mappedBy = "member")


  • 로거 잡을때 @Slf4j
    SLF4J(Simple Logging Facade for Java)
    java.util.logging, logback 및 log4j와 같은 다양한 로깅 프레임 워크에 대한 추상화(인터페이스) 역할을 하는 라이브러리이다.

  • @RequiredArgsConstructor 지정된 속성들에 대해서만 생성자를 만들어 줌

  • @Data
    🔺메모리 낭비가 있을 수 있다.
    @ToString, @EqualsAndHashCode, @Getter(모든 속성), Setter(final 이 아닌 속성),
    @RequiredArgsConstructor를 합쳐둔 어노테이션

⭐⭐⭐ @RequiredArgsConstructor

  • 생성자 방식으로 내부적으로 주입.
    private final ItemService itemService;

💻 결과화면 :




  • 타임리프 :
    DTO명 ${form}
    컬럼명 : *{author}

🔺액션이 없다? 액션이 없는 상태에서 submit url그대로.

영속성 관리때문에 따로 update를 작성하지 않아도 수정이 된다.

📌📋 ItemController.java

     @PostMapping(value = "/items/{itemId}/edit")
     public String updateItem(@ModelAttribute("form") BookForm form) {
	     log.info("updateItem itemId->{}",form.getId() );
	     log.info("updateItem getName->{}",form.getName());
	     
	  // 1. [controller-->Service ]-->updateItem(id, name , price)
	     //1. 하나씩 보내는 방법.
//	     itemService.updateItem(form.getId(), form.getName(), form.getPrice());
	     //2. DTO로 넘겨줌
	     itemService.updateItem(form);
	     
		 return "redirect:/items";

     }

📋 ItemService.java

	// 2. [Service   -->Repository ]--> 수정
	public void updateItem(BookForm form) {
		Item item = itemRepository.findOne(form.getId()); 
		item.setName(form.getName());
		item.setPrice(form.getPrice());
	}


🌱 Dirty Checking _ 변경감지


  • Entity 객체의 데이터만 변경하면 어떻게 DB에 수정된 데이터가 반영
    참고링크

Dirty Checking 동작방법:

JPA는 영속성 컨텍스트를 생성하여 DB와 유사하게 이용하여 Entity의 Life Cycle을 관리한다.
JPA는 영속성 컨텍스트에 Entity를 보관할 때 최초의 상태를 저장하고 있다. 이것을 스냅샷이라고 하며 영속성 컨텍스트가 Flush되는 시점에 스냅샷과 Entity의 현재 상태를 비교하여 달라진 Entity를 찾는다.

이후 변경된 필드들을 이용하여 쓰기지연 SQL 저장소에 Update 쿼리를 생성하여 쌓아 둔다.

모든 작업이 끝나고 트랜잭션을 커밋을 하면 이때 쓰기지연SQL 저장소에 있는 쿼리들을 DB에 전달하여 Update를 진행한다.


성능개선 : @Transactional(readOnly = true), 수정할 것엔 @Transactional 붙여주기


🌱주문하기, 리스트조회


  • 연관관계를 이용하여 작성

사용자가 입력할수 있는정보를 한정해서 작업.


📋 orderForm.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js" integrity="sha384-oBqDVmMz9ATKxIep9tiCxS/Z9fNfEXiDAYTujMAeBAsjFuCZSmKbSSUnQlmh/jp3" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.min.js" integrity="sha384-IDwe1+LCz02ROU9k972gdyvl+AESN10+x7tBKgc9I5HFtuNz0wWnPclzo6p9vxnk" crossorigin="anonymous"></script>
</head>
<body>
	<div class="container">
		<form role="form" action="/orderSave" method="post">
		 	<div class="form-group">
				<label for="member">주문회원</label>
		 		<select name="memberId" id="member" class="form-control">
					<option value="">회원선택</option>
					<option th:each="member : ${members}" th:value="${member.id}" th:text="${member.name}"/>		 		
				</select>
			</div>
		 	<div class="form-group">
				<label for="item">상품명</label>
		 		<select name="itemId" id="member" class="form-control">
					<option value="">상품선택</option>
					<option th:each="item : ${items}" th:value="${item.id}" th:text="${item.name}"/>		 		
				</select>
			</div>
		 	<div class="form-group">
				<label th:for="count">주문수량</label>
				<input type="number" name="count" class="form-control" id="count" placeholder="주문 수량을 입력하세요">
			</div>
			<button type="submit" class="btn btn-primary">Submit</button>
		</form>
	</div>
</body>
</html>



📋 OrderItem.java

package com.oracle.oBootJpa03.domain.item;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;

import com.oracle.oBootJpa03.domain.Order;

import lombok.Data;

@Entity
@Data
@SequenceGenerator(name = "order_item_seq",
				 sequenceName = "order_item_sequence",
				 initialValue = 1,
				 allocationSize = 1
		)

@Table(name = "order_item")
public class OrderItem {
	@Id
	@GeneratedValue(
			strategy = GenerationType.SEQUENCE,
			generator = "order_item_seq"
			)
	@Column(name = "order_item_id")
	private Long id;
	
	@ManyToOne
	@JoinColumn(name = "item_id")
	private Item item; // 주문상품
	
	@ManyToOne
	@JoinColumn(name = "order_id")
	private Order order; // 주문
	
	private int orderPrice; // 주문가격
	private int count; // 주문수량
	
}

2. 컨트롤러 작성


📌📋 HomeController.java

package com.oracle.oBootJpa03.controller;

import java.util.List;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import com.oracle.oBootJpa03.domain.Member;
import com.oracle.oBootJpa03.domain.item.Item;
import com.oracle.oBootJpa03.service.ItemService;
import com.oracle.oBootJpa03.service.MemberService;
import com.oracle.oBootJpa03.service.OrderService;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Controller
@Slf4j
@RequiredArgsConstructor
public class OrderController {

	private final OrderService  orderService;
	private final MemberService memberService;
	private final ItemService   itemService;
	
	@GetMapping(value = "order")
	public String createOrderForm(Model model) {
		log.info("OrderController createOrderForm START ================");
		List<Member> members = memberService.findMembers();
		List<Item> items = itemService.findItems();
		model.addAttribute("mebers", members);
		model.addAttribute("items", items);
		
		return "order/orderForm";
	}
	
	@PostMapping(value = "orderSave")
	public String orderSave(@RequestParam("memberId")	Long memberId,
							@RequestParam("itemId") 	Long itemId,
							@RequestParam("count")	int count) {
		log.info("order orderSave START ================");
		OrderService.order(memberId, itemId, count);
		return "redirect:/";
	}
}

JPA로 작업시엔 객체연관관계로 작업.

📌 객체연관관계 :


📌 테이블구조 :


🌱 즉시 로딩과 지연 로딩


  • 지연로딩을 해야하는 이유? Lazy ? Eager?
    즉시 로딩(EAGER)지연 로딩(LAZY)

즉시 로딩은 데이터를 조회할 때 연관된 데이터까지 한 번에 불러오는 것이고,
지연 로딩은 필요한 시점에 연관된 데이터를 불러오는 것

같이 특정 엔티티를 조회할 때 연관된 모든 엔티티를 같이 로딩하는 것을 즉시 로딩(Eager Loading) 이라고 합니다.
즉시로딩(FetchType - EAGER)은 엔티티를 조회할 때 연관된엔티티를 함께 조회합니다.

지연 로딩(Lazy Loading) 이란, 가능한 객체의 초기화를 지연시키는데 사용하는 패턴.

fetch?

@ManyToOne(fetch = FetchType.LAZY)

profile
공부중인 주니어 개발자

0개의 댓글