Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[배성우/고도] 7주차 정리내용 제출. #88

Open
wants to merge 2 commits into
base: Spring-Wed-a
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Godot-B/READMEs/실전! 스프링 부트와 JPA 활용 1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# 실전! 스프링 부트와 JPA 활용 1

[memo](%E1%84%89%E1%85%B5%E1%86%AF%E1%84%8C%E1%85%A5%E1%86%AB!%20%E1%84%89%E1%85%B3%E1%84%91%E1%85%B3%E1%84%85%E1%85%B5%E1%86%BC%20%E1%84%87%E1%85%AE%E1%84%90%E1%85%B3%E1%84%8B%E1%85%AA%20JPA%20%E1%84%92%E1%85%AA%E1%86%AF%E1%84%8B%E1%85%AD%E1%86%BC%201%2013dd916bc5a8803f9fcfdf003750cf8f/memo%20141d916bc5a88077b5cecf83411bc0ee.md)

[도메인 분석 설계](%E1%84%89%E1%85%B5%E1%86%AF%E1%84%8C%E1%85%A5%E1%86%AB!%20%E1%84%89%E1%85%B3%E1%84%91%E1%85%B3%E1%84%85%E1%85%B5%E1%86%BC%20%E1%84%87%E1%85%AE%E1%84%90%E1%85%B3%E1%84%8B%E1%85%AA%20JPA%20%E1%84%92%E1%85%AA%E1%86%AF%E1%84%8B%E1%85%AD%E1%86%BC%201%2013dd916bc5a8803f9fcfdf003750cf8f/%E1%84%83%E1%85%A9%E1%84%86%E1%85%A6%E1%84%8B%E1%85%B5%E1%86%AB%20%E1%84%87%E1%85%AE%E1%86%AB%E1%84%89%E1%85%A5%E1%86%A8%20%E1%84%89%E1%85%A5%E1%86%AF%E1%84%80%E1%85%A8%2013fd916bc5a880cdb24ac6fc587cb9af.md)

[Member 도메인 개발](%E1%84%89%E1%85%B5%E1%86%AF%E1%84%8C%E1%85%A5%E1%86%AB!%20%E1%84%89%E1%85%B3%E1%84%91%E1%85%B3%E1%84%85%E1%85%B5%E1%86%BC%20%E1%84%87%E1%85%AE%E1%84%90%E1%85%B3%E1%84%8B%E1%85%AA%20JPA%20%E1%84%92%E1%85%AA%E1%86%AF%E1%84%8B%E1%85%AD%E1%86%BC%201%2013dd916bc5a8803f9fcfdf003750cf8f/Member%20%E1%84%83%E1%85%A9%E1%84%86%E1%85%A6%E1%84%8B%E1%85%B5%E1%86%AB%20%E1%84%80%E1%85%A2%E1%84%87%E1%85%A1%E1%86%AF%2013fd916bc5a8808983bdc7c3753ba720.md)

[Item 도메인 개발](%E1%84%89%E1%85%B5%E1%86%AF%E1%84%8C%E1%85%A5%E1%86%AB!%20%E1%84%89%E1%85%B3%E1%84%91%E1%85%B3%E1%84%85%E1%85%B5%E1%86%BC%20%E1%84%87%E1%85%AE%E1%84%90%E1%85%B3%E1%84%8B%E1%85%AA%20JPA%20%E1%84%92%E1%85%AA%E1%86%AF%E1%84%8B%E1%85%AD%E1%86%BC%201%2013dd916bc5a8803f9fcfdf003750cf8f/Item%20%E1%84%83%E1%85%A9%E1%84%86%E1%85%A6%E1%84%8B%E1%85%B5%E1%86%AB%20%E1%84%80%E1%85%A2%E1%84%87%E1%85%A1%E1%86%AF%20140d916bc5a880bb9faaea0ce5143831.md)

[Order 도메인 개발](%E1%84%89%E1%85%B5%E1%86%AF%E1%84%8C%E1%85%A5%E1%86%AB!%20%E1%84%89%E1%85%B3%E1%84%91%E1%85%B3%E1%84%85%E1%85%B5%E1%86%BC%20%E1%84%87%E1%85%AE%E1%84%90%E1%85%B3%E1%84%8B%E1%85%AA%20JPA%20%E1%84%92%E1%85%AA%E1%86%AF%E1%84%8B%E1%85%AD%E1%86%BC%201%2013dd916bc5a8803f9fcfdf003750cf8f/Order%20%E1%84%83%E1%85%A9%E1%84%86%E1%85%A6%E1%84%8B%E1%85%B5%E1%86%AB%20%E1%84%80%E1%85%A2%E1%84%87%E1%85%A1%E1%86%AF%20140d916bc5a880a2bf97c37968fe6226.md)

[웹 계층 개발](%E1%84%89%E1%85%B5%E1%86%AF%E1%84%8C%E1%85%A5%E1%86%AB!%20%E1%84%89%E1%85%B3%E1%84%91%E1%85%B3%E1%84%85%E1%85%B5%E1%86%BC%20%E1%84%87%E1%85%AE%E1%84%90%E1%85%B3%E1%84%8B%E1%85%AA%20JPA%20%E1%84%92%E1%85%AA%E1%86%AF%E1%84%8B%E1%85%AD%E1%86%BC%201%2013dd916bc5a8803f9fcfdf003750cf8f/%E1%84%8B%E1%85%B0%E1%86%B8%20%E1%84%80%E1%85%A8%E1%84%8E%E1%85%B3%E1%86%BC%20%E1%84%80%E1%85%A2%E1%84%87%E1%85%A1%E1%86%AF%20146d916bc5a880afb49fe7a09a811421.md)
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
# Item 도메인 개발

# 엔티티 메서드

---

## 비즈니스 로직

---

- 재고를 넣고 빼는 로직
- 현재 스톡 수량 정보를 사용하기 때문에, 핵심 비즈니스 로직을 엔티티에 직접 넣었음.

item 서비스에 비즈니스 로직을 넣지 않고, 데이터를 가지고 있는 쪽에 비즈니스 메소드가 있는게 가장 좋다. ⇒ 응집력이 있음


```java
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "dtype")
@Getter @Setter
public abstract class Item {

@Id
@GeneratedValue
@Column(name = "item_id")
private Long id;

private String name;
private int price;
private int stockQuantity;

@ManyToMany(mappedBy = "items")
private List<Category> categories = new ArrayList<>();

//==비즈니스 로직==//

/**
* stock 증가
*/
public void addStock(int quantity) {
this.stockQuantity += quantity;
}

/**
* stock 감소
*/
public void removeStock(int quantity) {
int restStock = this.stockQuantity - quantity;
if(restStock < 0) {
throw new NotEnoughStockException("need more stock");
}
this.stockQuantity = restStock;
}
}
```

### NotEnoughStockException 예외 만들기

---

- RuntimeException 예외를 상속받아서 오버라이딩

```java
package jpabook.jpashop.exception;

public class NotEnoughStockException extends RuntimeException {

public NotEnoughStockException() {
super();
}

public NotEnoughStockException(String message) {
super(message);
}

public NotEnoughStockException(String message, Throwable cause) {
super(message, cause);
}

public NotEnoughStockException(Throwable cause) {
super(cause);
}

protected NotEnoughStockException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

```

# ItemRepository

---

## save()

---

```java
@Repository
@RequiredArgsConstructor
public class ItemRepository {

private final EntityManager em;

public void save(Item item) {
if (item.getId() == null) {
em.persist(item);
} else {
em.merge(item);
}
}
}
```

### persist()

---

신규 등록

### merge()

---

업데이트 (와 비슷한 개념)

→ 나중에 더 구체적으로 설명

## findOne()과 findALL()

---

- 단건 조회는 find로 가능하지만
- 여러 개 찾는 것은 JPQL을 작성해야한다.

```java
public Item findOne(Long id) {
return em.find(Item.class, id);
}

public List<Item> findAll() {
return em.createQuery("select i from Item i ", Item.class)
.getResultList();
}
```

# ItemServce

---

- ItemRepository를 단순 위임하는 형태

```java
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class ItemService {

private final ItemRepository itemRepository;

@Transactional
public void saveItem(Item item) {
itemRepository.save(item);
}

public List<Item> findItems() {
return itemRepository.findAll();
}

public Item findOne(Long itemId) {
return itemRepository.findOne(itemId);
}
}
```

- 이런 경우에는 컨트롤러가 굳이 서비스 거칠 필요 없이 리포지토리 접근하게 설계해도 괜찮음.

---

테스트는 이전 Member 기능과 유사하므로 생략
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# Member 도메인 개발

# MemberRepository

---

- `@Repository` 는 `@SpringBootApplication` 의 컴포넌트 스캔의 대상이 되어 Spring Bean으로 등록
- `@PersistenceContext` 어노테이션이 있으면 Spring이 생성한 JPA의 Entity Manager를 주입을 해준다. [하지만 RequiredArgsConstructor 를 사용하자](Member%20%E1%84%83%E1%85%A9%E1%84%86%E1%85%A6%E1%84%8B%E1%85%B5%E1%86%AB%20%E1%84%80%E1%85%A2%E1%84%87%E1%85%A1%E1%86%AF%2013fd916bc5a8808983bdc7c3753ba720.md)

![image.png](Member%20%E1%84%83%E1%85%A9%E1%84%86%E1%85%A6%E1%84%8B%E1%85%B5%E1%86%AB%20%E1%84%80%E1%85%A2%E1%84%87%E1%85%A1%E1%86%AF%2013fd916bc5a8808983bdc7c3753ba720/image.png)

### save()와 findOne()

---

```java
public void save(Member member) {
em.persist(member);
}

public Member findOne(Long id) {
return em.find(Member.class, id);
}
```

## JPQL

---

*자세한 것은 기본편*

기능적으로 SQL과 동일하다. (결국 SQL로 번역이 돼야 하기 때문에)

SQL과의 차이점

- SQL은 테이블을 대상으로 쿼리를 작성
- JPQL은 entity-객체를 대상으로 쿼리를 작성
- 예 : from의 대상이 테이블이 아니라 **엔티티**

### findALL()

---

```java
public List<Member> findAll() {

List<Member> result = em.createQuery("select m from Member m", Member.class)
.getResultList();
return result;
}
```

- 여기서 List 필드 이름에 **Ctrl + Alt + N** 리팩토링을 하면 줄여짐.

```java
public List<Member> findAll() {
return em.createQuery("select m from Member m", Member.class)
.getResultList();
}
```

### findByName()

---

```java
public List<Member> findByName(String name) {
return em.createQuery("select m from Member m where m.name = :name", Member.class)
.setParameter("name", name)
.getResultList();
}
```

# MemberService

---

## @Transactional

---

- 기본적으로 **모든 데이터 변경과 관련된 로직들**은 Transaction 안에서 실행이 돼야한다.
- 읽기는 `@Transactional(readOnly=true)` 옵션을 주면 최적화된다.
- **default는** readOnly=**false**임. 아래 용법 주목 (읽기 로직이 많으므로 이렇게 한다)

```java
@Service
@Transactional(readOnly = true)
public class MemberService {

... ...

//회원 가입
@Transactional
public Long join(Member member) {
...
}

//회원 전체 조회
public List<Member> findMembers() {
...
}

public Member findOne(Long memberId) {
...
}
}

```

- `@Transactional` 은 spring이 제공하는 어노테이션을 쓰자. (쓸 수 있는 옵션 많음)

### 회원 가입

---

```java
@Transactional
public Long join(Member member) {
validateDuplicateMember(member); //중복 회원 검증
memberRepository.save(member);
return member.getId();
}

private void validateDuplicateMember(Member member) {
List<Member> findMembers = memberRepository.findByName(member.getName());
if (!findMembers.isEmpty()) {
throw new IllegalStateException("이미 존재하는 회원입니다.");
}
}
```

참고 : 실무에서는 검증 로직이 있어도 멀티 쓰레드 상황 (예 : 두 명이 정말 동시에 같은 이름으로 등록이 되는 경우 등) 을 고려해서 회원 테이블의 회원명 칼럼에 유니크 제약 조건을 추가하는 것이 안전하다.

### 회원 조회

---

```java
public List<Member> findMembers() {
return memberRepository.findAll();
}

public Member findOne(Long memberId) {
return memberRepository.findOne(memberId);
}

```

## `@RequiredArgsConstructor`

---

- 코어편 내용: `@RequiredArgsConstructor` 는 `@AllArgsConstructor` 와 다르게, final 필드에만 생성자 주입이 되도록 한다.
- JPA 내용 :
- 스프링 부트에서 `@Autowired`부터 `@RequiredArgsConstructor` 는 Entity Manager의 주입에 대해서도 해결해 줌!
- 따라서 MemberRepository에서 [@PersistenceContext 를 쓰며 final 도 없던 것](Member%20%E1%84%83%E1%85%A9%E1%84%86%E1%85%A6%E1%84%8B%E1%85%B5%E1%86%AB%20%E1%84%80%E1%85%A2%E1%84%87%E1%85%A1%E1%86%AF%2013fd916bc5a8808983bdc7c3753ba720.md) ,이 → 이렇게 가능

![image.png](Member%20%E1%84%83%E1%85%A9%E1%84%86%E1%85%A6%E1%84%8B%E1%85%B5%E1%86%AB%20%E1%84%80%E1%85%A2%E1%84%87%E1%85%A1%E1%86%AF%2013fd916bc5a8808983bdc7c3753ba720/image%201.png)


[회원 기능 테스트](Member%20%E1%84%83%E1%85%A9%E1%84%86%E1%85%A6%E1%84%8B%E1%85%B5%E1%86%AB%20%E1%84%80%E1%85%A2%E1%84%87%E1%85%A1%E1%86%AF%2013fd916bc5a8808983bdc7c3753ba720/%E1%84%92%E1%85%AC%E1%84%8B%E1%85%AF%E1%86%AB%20%E1%84%80%E1%85%B5%E1%84%82%E1%85%B3%E1%86%BC%20%E1%84%90%E1%85%A6%E1%84%89%E1%85%B3%E1%84%90%E1%85%B3%2013fd916bc5a8805a9974e15fc3034f28.md)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading