항목나열.
▶ 편입시키기 위해선 @Enumerated(EnumType.STRING)
사용.
⭐ Original로 쓰지말아라
@JoinColumn(name = "member_id")
을 사용
readOnly
를 사용:@Transactional(readOnly = true)
❓ 왜
readOnly = true
를 써야하나?
▶ 안 써도 된다. 하지만 사용하면 성능 최적화 및 실수로 데이터를 변경하는 일을 방지할 수도 있다.
readOnly
를 꼭 써야하는줄 알았다. 그런데 아래 글을 보고 생각이 달라졌다. @Transactional(ReadOnly = true)
가 나머지(save, update, delete)에 대해선 @Transactional이 자동으로 사용되어진다.@Transactional
을 사용하면 Spring AOP 가 해당 메소드 앞, 뒤에 transaction 시작과 transaction.commit() 을 실행해준다.
➡ SAVE하는것에 사용.(select 외에것.)
👨🏫say :
ManyToOne일때 ➡ LAZY. LAZY로 다 거는게 좋다. 기본 LAZY.
EAGER
지연로딩을 사용하려면 실제 엔티티 객체 대신에 데이터베이스 조회를 지연할 수 있도록 지원하는 가짜 객체가 필요한데 이를 프록시라고 한다.
(1) 프록시 객체에 메소드를 호출하면, 초기화가 진행된다.
영속성 컨텍스트에 실제 엔티티가 생성되어 있지 않으면, 데이터베이스 접근하여 데이터를 가져와 실제 엔티티를 생성한다.
(2) 프록시 Entity target 인스턴스에 생성된 인스턴스의 참조를 할당한다.
(3) 프록시 객체는 실제 객체(즉, target의 참조변수)의 메소드를 호출한다.
지연로딩(LAZY)은 연관된 엔티티를 실제 사용할 때 조회한다. (Quesion을 조회할 때, List도 사용한다면 그때만 조회)
ManyToOne
, OneToOne
▶ 즉시로딩OneToMany
, ManyToMany
▶ 지연로딩 을 사용!@ManyToOne(fetch = FetchType.LAZY)
👨🏫이 알려주신건 계속 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를 합쳐둔 어노테이션
private final ItemService itemService;
🔺액션이 없다? 액션이 없는 상태에서 submit url그대로.
영속성 관리때문에 따로 update를 작성하지 않아도 수정이 된다.
@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";
}
// 2. [Service -->Repository ]--> 수정
public void updateItem(BookForm form) {
Item item = itemRepository.findOne(form.getId());
item.setName(form.getName());
item.setPrice(form.getPrice());
}
JPA는 영속성 컨텍스트를 생성하여 DB와 유사하게 이용하여 Entity의 Life Cycle을 관리한다.
JPA는 영속성 컨텍스트에 Entity를 보관할 때 최초의 상태를 저장하고 있다. 이것을 스냅샷이라고 하며 영속성 컨텍스트가 Flush되는 시점에 스냅샷과 Entity의 현재 상태를 비교하여 달라진 Entity를 찾는다.
이후 변경된 필드들을 이용하여 쓰기지연 SQL 저장소에 Update 쿼리를 생성하여 쌓아 둔다.
모든 작업이 끝나고 트랜잭션을 커밋을 하면 이때 쓰기지연SQL 저장소에 있는 쿼리들을 DB에 전달하여 Update를 진행한다.
성능개선 : @Transactional(readOnly = true)
, 수정할 것엔 @Transactional
붙여주기
사용자가 입력할수 있는정보를 한정해서 작업.
<!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>
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; // 주문수량
}
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:/";
}
}
즉시 로딩은 데이터를 조회할 때 연관된 데이터까지 한 번에 불러오는 것이고,
지연 로딩은 필요한 시점에 연관된 데이터를 불러오는 것
같이 특정 엔티티를 조회할 때 연관된 모든 엔티티를 같이 로딩하는 것을 즉시 로딩(Eager Loading) 이라고 합니다.
즉시로딩(FetchType - EAGER)은 엔티티를 조회할 때 연관된엔티티를 함께 조회합니다.
지연 로딩(Lazy Loading) 이란, 가능한 객체의 초기화를 지연시키는데 사용하는 패턴.
fetch?
@ManyToOne(fetch = FetchType.LAZY)