MemberController: 회원 등록 컨트롤러 

1. createForm 

model의 역할: 컨트롤러에서 뷰로 넘어갈 때 model을 통해 넘긴다.

 @GetMapping(value = "/members/new")
    public String createForm(Model model) {
        model.addAttribute("memberForm", new MemberForm());
        return "members/createMemberForm";
    }

사용자가 사용자 정보를 입력할 수 있는 페이지를 만드는 메소드.

addAttribute("memberForm", new MemberForm());에서 빈 MemberForm을 생성해서 넘겨야 에러가 발생하지 않는다 

 

MemberForm.class로 반환했더니 생겼던 에러는 다음과 같다 

 

2. create()

@PostMapping(value = "/members/new")
    public String create(@Valid MemberForm form, BindingResult result) {
        if (result.hasErrors()) {
            return "members/createMemberForm";
}
        Address address = new Address(form.getCity(), form.getStreet(),
form.getZipcode());
        Member member = new Member();
        member.setName(form.getName());
 member.setAddress(address);
          memberService.join(member);
          return "redirect:/";
      }

 

@Valid: request 후에 서버측에서 데이터를 바인딩할때, 데이터가 유효한지(ex. 누락, 최대 크기 초과 등) 검사해야 하는 경우에 사용하는 어노테이션이다. 객체에 들어가는 값을 검증해준다

MemberForm에 name 필드에 @NotEmpty가 설정되어있으므로 @Valid MemberForm form 형태로 파라미터를 작성한다 

 

@Valid를 사용하기 위해서는 설정이 필요한데 다음고 같이 하면 된다. 강의와는 그레이들 버전이 달라서 버전에 맞춰서 진행하면 된다 

 

/Controller/MemberForm

@NotEmpty(message = "회원 이름은 필수 입니다 ")
    private String name;

BindingResult: Validator를 상속받는 클래스에서 객체값을 검증하는 방식이다

if (result.hasErrors()){ return "members/createMemberForm";} => result에 에러가 있다면 회원 등록 html로 이동

 

입력된 사용자 정보를 저장하는 메소드이다.

form에는 사용자의 이름, 도시,주소명,우편번호가 담겨져있다

각각의 필드를 객체에 저장하고 join한다. 

redirect:/ => 가입이 완료되면 메인페이지로 이동한다

 

3. list()

@GetMapping(value = "/members") public String list(Model model) {
          List<Member> members = memberService.findMembers();
          model.addAttribute("members", members);
          return "members/memberList";
      }

조회한 상품을 view에 전달하기 위해 스프링 mvc가 제공하는 model 객체에 보관한다(members 객체를 "members"에 담아서 뷰에 전달한다)

 

실무에서는 엔티티를 외부에 노출하거나, 화면에 반환하는 일은 절대 없어야한다 

엔티티를 노출하면 엔티티가 화면 종속성이 증가할 수 있고, 나중에 유지보수를 하기 어려워질 수 있다.

따라서 엔티티는 핵심 비즈니스 로직만 가지고 있고, 화면을 위한 로직은 없어야한다. 

==> 화면이나 API에 맞는 폼 객체나 DTO를 사용하자 

 

 

 

1. 복습 

cascade하면 부모 객체만 저장하면 관련된 자식 객체는 자동으로 persist 된다 

2. 애플리케이션 아키텍쳐

-계층형 구조 사용

controller,web: 웹 계층

service: 비즈니스 로직, 트랜잭션 처리

repository: JPA를 직접 사용하는 계층, 엔티티 매니저 사용

domain: 엔티티가 모여 있는 계층, 모든 계층에서 사용

 

3. @SpringBootTest

컴포넌트 스캔을 해서 스프링 빈에 컴포넌트를 등록한다 

4. 테스트 시 Rollback 하는 이유

: db에 테스트 데이터를 남기지 않기 위해서  

5. 스프링 부트가 설정이 없으면 기본으로 메모리 모드로 돌린다

6.  엔티티에 비즈니스 로직 

엔티티 안에 비즈니스 로직을 넣는 것이 좋다(객체지향적인 방법)

데이터를 가지고 있는 곳에서 로직을 실행하는 것이 응집력이 있음

 

1) Order.class

 

- 생성 메서드

Order에서 참조하는 member,delivery,orderItem을 파라미터로 받고 set으로 설정해준다

orderItems는 리스트이므로 반복문으로 하나씩 order에 추가해준다 (여기가 익숙하지 않다)

order가 생성된 것이므로, orderStatus는 ORDER로 설정하고, 현재 시간을 설정해준다 

 

-비즈니스 로직: 주문을 취소한 경우에 대해 구현했다

먼저 해당 상품이 배송중인지 아닌지를 확인한다 

배송중이라면 illegalstateexception("이미 배송 완료된 상품은 취소가 불가능합니다") 설정하고, OrderStatus를 CANCEL로 바꿔준다(setStatus)

배송중인 상품이 아니라면, 각각의 orderItems에 대해서 cancel을 실행한다

 

-조회로직: 주문갯수와 가격을 고려해서 총가격을 가져오는 메소드이다 

 

2) OrderItem.class

 

-OrderItems 생성메서드 

아이템을 구성하는 것들을 set으로 설정한다음 item이 stock에서 count만큼 줄인다

 

 

-OrderItems 취소 비즈니스 로직

: 취소한만큼의 stock을 item에 추가해준다 

 

-주문상품 전체 가격조회 

전체 가격은 주문 가격*갯수이므로 그것으로 구현해준다 

 

7.  현재 문맥에서 가장 적합한 패턴 사용하기(예제에서는 도메인 모델 패턴)

-도메인 모델 패턴: 엔티티가 비즈니스 로직을 가지고 객체 지 향의 특성을 적극 활용하는 것

8. 연관관계 편의 메소드

양방향 연관관계를 가진 엔티티들에서 값 변경이 일어날때 양쪽 모두에 변경된 값을 입력을 할 수 있게 하는 메소드이다 

강의: 인프런 JPA+SpringBoot 활용 1편 

1. jpa 기본 내용 복습

- 같은 트랜잭션 안에서는 영속성 컨텍스트 조회. 같은 영속성 컨텍스트에서 아이디 같으면 같은 엔티티로 인식. 1차 캐시에서 가져옴

- 값 타입은 변경 불가능하게 설정하기 

- 실무에서는 @ManyToMany 사용하지 않기 

2. 개발 시작 단계에서 해야할 것들

-요구사항 분석

-도메인 모델 설계

-엔티티 설계

-테이블 설계: DB에서는 소문자+_ 형태로 주로 사용한다

3. 테이블에서 외래키가 있는 곳을 연관관계의 주인으로 정하기 

4. 쿼리 파라미터 확인하는 방법 : p6spy

쿼리 파라미터 로그 남기는p6spy를 이용해서쿼리 파라미터를 확인   있지만, 잘못하면 성능을 저하시키거나 병목현상을 발생시킬  있다. 되도록이면 개발 단계에서 하는 것을 추천 

p6spy이용했을 때 출력된 쿼리. 파리미터를 확인할 수 있다&amp;amp;amp;nbsp;

5. 엔티티 설계시 주의점 

 

1) 엔티티에는 가급적 Setter를 사용하지 말자

setter를 사용하면 변경 포인트가 많아져서 유지보수가 어려워진다

 

2) 모든 연관관계는 지연로딩으로 설정하자(fetch=FetchType.LAZY)

즉시 로딩은 예측이 어렵고, 어떤 sql 실행될지 추적하기가 어렵다jpql에서 n+1 문제가 발생할 있기 때문이다 

 

3) 컬렉션은 필드에서 초기화하자(@RequiredArgstructor, private final 이용)

이렇게 하면 null 문제에서 안전하고, 임의의 메서드에서 컬렉션을 잘못 생성하면 하이버네이트 내부 메커니즘에 문제가 발생할 수 있다. 

 

6. em.find() vs JPQL

em.find(): 영속성 컨텍스트를 먼저 조회하고 없으면 SQL을 실행한다

JPQL: SQL을 먼저 실행한다 

 

7. @Controller vs @RestController

@Controller: view를 반환하기 위한 Controller 개발 시 사용한다

강의에서는 thymeleaf를 통해 서버사이드에서 view를 렌더링하기위해 @Controller를 사용했다 

 

@ResponseBody(Controller+ResponseBody): Data(Json Format)을 반환하기 위해 사용한다 

vue.js를 통해 프론트개발을 한다거나 단순히 data를 반환해야하는 api를 개발할 때 사용 

 

8. @Transactional

DB에 접근하는 메소드를 작성할 때 @Transactional 어노테이션을 추가해야 다음과 같은 에러가 발생하지 않는다 

 

이유는 기본적으로 JPA는 transaction을 기반으로 작동하게 되어있다. 

transation 단위에 따라 1차캐시 영역에 있는 객체들이 db로 flush되어 영속화되기 때문이다

 

하지만 그러한 영속작업을 하는 persist()메소드에 객체가 들어갔으나 가능한 transaction이 존재하기 않았기에 위와같은 에러가 발생했다.

변경을 하지 않는 메소드에는 @Transactional(readOnly=true)를 선언하자

 

 

9. 하나의 엔티티에서 두 필드가 서로를 참조하는 경우(부모-자식관계)

1. 페치 조인(fetch join): 실무에서 정말정말 중요하다

sql의 조인 종류가 아니라 JPQL에서 성능 최적화를 위해 제공하는 기능이다 

연관된 엔티티나 컬렉션을 SQL에 한번에 함께 조회하는 기능이다

join fetch 명령어를 사용한다 

페치 조인 ::= [LEFT[OUTER]|[INNER] JOIN FETCH 조인경로

 

-엔티티 페치 조인

회원을 조회하면서 연관된 팀도 함께 조회한다(SQL로 한번에) 

SQL을 보면 회원 뿐만 아니라 팀(T.*)도 함께 SELECT 한다 

JPQL: select m from Member m join fetch m.team

SQL: select m.*,t.* from member m inner join team t on m.team_id =t.id

 

 

 

-컬렉션 페치 조인

일대다 관계에서 컬렉션 페치 조인을 사용한다 

JPQL : select t from Team t join fetch t.members where t.name='팀A'

SQL: select t.*,m.* from Team t inner join member m on m.team_id=t.id  where t.name='팀A'

 

 

 

-페치 조인과 DISTINCT

DISTINCT가 추가로 애플리케이션에서 중복 제거를 시도한다 

같은 식별자를 가진 Team 엔티티를 제거한다 

 

-페치 조인과 일반 조인의 차이

일반 조인 실행했을 때는 연관된 엔티티를 함께 조회하지 않는다 

JPQL: select t from Team t join t.members m where t.name='팀A'

SQL: select t.* from Team t inner join Member m on m.team_id =t.id where t.name='팀A'

JPQL은 결과를 반환할 때 연관관계를 고려하지 않는다. 단지 select 절에 지정한 엔티티만 조회한다.

여기서는 팀 엔티티만 조회하고, 회원 엔티티는 조회하지 않는다 

 

페치 조인을 사용할 때만 연관된 엔티티도 함께 조회한다(즉시 로딩)

페치 조인은 객체 그래프를 SQL 한번에 조회하는 개념이다

일대다 조인은 데이터가 뻥튀기 될 수 있다  

JPQL: select t from Team t join fetch t.members where t.name='팀A'

SQL: select t.*,m.* from team t inner join member m on t.id=m.team_id where t.name='팀A'

 

-페치 조인의 특징과 한계 

 

1) 특징

연관된 엔티티들을 SQL 한번으로 조회한다. 성능이 최적화한다

엔티티에 직접 적용하는 글로벌 로딩 전략보다 우선한다 

@OneToMany(fetch=FetchType.LAZY) //글로벌 로딩 전략 

실무에서 글로벌 로딩 전략은 모두 지연로딩

최적화가 필요한 곳은 페치 조인을 적용한다 

 

2)한계 

페치 조인 대상에는 별칭을 줄 수 없다. 하이버네이트에서는 가능 하지만, 가급적 사용하지 않는다 

페치 조인에서 컬렉션은 하나만 지정할 수 있다

둘 이상의 컬렉션은 페치 조인을 할 수 없다 

컬렉션을 페치 조인하면 페이징 API(setFirstResult,setMaxResults)를 사용할 수 없다.   

일대일, 다대일 같은 단일 값 연관 필드들은 페치 조인해도 페이징이 가능하지만, 하이버네이트는 경고 로그를 남기고 메모리에서 페이징한다(매우 위험하다)

페치조인에 페이징을 쓰지 못하는데 사용해야하는 경우에는 

@BatchSize를 추가하거나 설정에서 batchsize 의존성을 추가한다 

 

 

=====> 정리

모든 것을 페치 조인으로 해결할 수는 없다

페치 조인은 객체 그래프를 유지할 때 사용하면 효과적이다 

여러 테이블을 조인해서 엔티티가 가진 모양이 아닌 전혀 다른 결과를 내야하면, 페치 조인 보다는 일반 조인을 사용하고 필요한 데이터들만 조회해서 dto로 반환하는 것이 효과적이다 

 

3가지 방법

  1. 엔티티를 페치조인으로 조회한다 그걸 쓴다
  2. 페치조인을 써서 애플리케이션에서 dto 바꿔서 뷰에 반환
  3. 처음부터 jpql   new operation으로 dto 반환해서 불러온다

 

2. 다형성 쿼리(TYPE,TREAT)

-TYPE

: type(i) in (a,b), 조회 대상을 특정 자식으로 한정한다 

예시) Item 중에 Book, Movie를 조회해라 

JPQL: select i from Item i where type(i) IN (Book,Movie)

SQL: select i from i where i.DTYPE in('B','M')

 

-TREAT

: Treat(parent as child), 자바의 타입 캐스팅과 유사하다 

상속 구조에서 부모 타입을 특정 자식 타입으로 다룰 때 사용한다 

from,where,select(하이버네이트 지원)에서 사용한다 

예시) 부모인 Item과 자식 Book이 있다

JPQL: select i from Item i where treat(i as Book).author='Kim'

SQL: select i.* from Item i where i.DTYPE='B' and i.author='Kim'

 

3. JPQL- 엔티티 직접 사용 

 

-기본키 값:

JPQL에서 엔티티를 직접 사용하면 SQL에서 해당 엔티티의 기본키 값을 사용한다 (엔티티 사용-> 엔티티 PK값 사용)

JPQL:

select count(m.id) from Member m //엔티티의 아이디를 사용

select count(m) from Member m // 엔티티를 직접 사용

SQL:  select count(m.id) as cnt from Member m 

 

 

4. Named 쿼리 

미리 정의해서 이름을 부여해두고 사용하는 JPQL이다 

정절 쿼리이며 어노테이션/XML에 정의한다

애플리케이션 로딩 시점에 초기화 후 재사용한다

애플리케이션 로딩 시점에 쿼리를 검증한다 

 

-Named 쿼리 환경에 따른 설정

xml이 항상 우선권을 가진다

애플리케이션 운영 환경에 따라 다른 xml을 배포할 수 있다 

 

5. 벌크 연산 

만약 재고가 10개 미만인 모든 상품의 가격을 10% 상승하려면? 

JPA 변경 감지 기능으로 실행하려면 너무 많은 SQL을 실행해야한다 

( 재고가 10개 미만인 상품을 리스트로 조회한다 -> 상품 엔티티의 가격을 10% 증가 시킨다 -> 트랜잭션 커밋 시점에 변경감지가 동작한다)

이때 변경된 데이터가 100건이라면 100번의 UPDATE SQL을 실행해야한다 

 

 

-주의 

벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에서 직접 쿼리한다 

벌크 연산을 먼저 실행-> 영속성 컨텍스트를 초기화한다 

 

 

 

 

 

 

+ Recent posts