jpa-2-활용(1)

김민지·2022년 12월 11일
0

JPA

목록 보기
23/27
post-custom-banner

디렉터리분리

controller는 api와 controller 디렉터리를 분리한다

  • 에러가 났을때 화면처리같은것을 보통 디렉터리단위로하는데 api에서 에러가났을경우에는 json으로 에러메세지를 줘야하는 반면 html을 뿌려주는 controller에서 에러가났을 경우에는 에러html페이지를 보여주어야하기 때문

RestController

controller + responseBody -> RestController
그니까 응답을 html말고 json으로 해주는것이 RestController

RequestBody

@RequestBody: Json으로 온 데이터를 @RequestBody가 붙은 객체에 넣어준다

inner class를 사용할때?

  • 이너 클래스는 이너 클래스를 포함하는 클래스 안에서만 한정적으로 접근할 때만 사용합니다.
  • 이너 클래스는 이너 클래스를 포함하는 클래스 안에서만 사용되므로, 개발자 입장에서 이너 클래스를 딱 봤을 때, 아! 이거는 이 안에서만 사용해야겠구나 하고, 생각의 범위를 줄일 수 있습니다.

@vaild

  • 유효성 검사가 필요한 객체앞에다가 선언한다. 그리고 그 객체에 vaild가 제공하는 어노테이션들을 붙여준다. 예를들어 @notEmpty를 붙였는데 들어오는 데이터가 null이라면 에러메세지를 뱉는다

코드의 문제상황

  • 다음코드의 문제가 무엇일까?
    <entity에 @vaild를 추가한 코드>
    이 @vaild는 외부에서 데이터를 넘겨줄때 제약조건을 검사하는일을수행한다
    같은 엔티티라도 notEmpty가 필요한 데이터가있고 필요없는 데이터가 있을 수 있다. 이런일 때문에 entity 와 dto를 분리하여야한다

  • 내가 만약에 entity의 컬럼을 username 으로 바꾸었다. 이것은 무엇을 의미하는가?

  • api spec이 바뀜을 의미한다. 즉, 프론트(클라이언트)에서 백엔드로 데이터를 요청할때 name으로 요청을했던 코드를 모두 username으로 바꿔야하는것을 의미
    -> entity를 손댔는데 api spec이 변화하는게 문제

  • 그리고 보통 큰 규모의 서비스의 경우 회원가입도 여러종류의 회원가입이 있을 수 있다 -> entity하나로 감당할 수 없다

  • 그리고 entity로만 받으면 그중에 일부데이터만 들어올거임. 그래서 클래스만 보고는 어떤 데이터가 화면에서 필요한 데이터인지를 알 수가없음

entity는 노출x, parameter로 넘겨도x

put vs post

POST는 Create(생성), PUT은 Update(수정)에

  • 요청한 횟수마다 새로운 리소스를 생성한다.
  • PUT은 요청 시 마다, 같은 리소스를 반환한다. 리소스 안에 속성은 변경될 수 있다.

patch vs put

PATCH는 수정만 담당하며 리소스의 일부분만 수정할 때 사용하고, PUT은 리소스의 모든 속성을 수정하기 위해 사용한다.

update dto vs save dto 분리하는 이유

membersaveDto만있으면 memberupdatedto를 만들지 않아도돼요
둘다 만들게 되면 코드의 중복이 발생하는것아닌가요?
save와 update는 아예다른것이기때문에 중복이 발생해도 상관이 없는걸까요?
아예다른것이라기엔 update에도 있고 save에도 있는 필드에 변화가 생겼을때 둘다 수정을 해주어야해요
그럼 수정해야할 포인트가 늘어나는건데도 updateDto를 생성해주는게 맞는걸까요?

다음 코드가 문제가 되는 이유

@Transactinal
public vodi update(Long id, String name){
	Member member = memberRepository.findOne(id);
    member.setName(name);
}

이런방식으로 update하기보다는 메서드로 묶어서 이 행위가 update를 하는 행위다 라는것을 명시해주는 것이 좋음

@Transactinal
public vodi update(Long id, String name){
	memberService.update(id,name);
    Member member = memberService.findOne(id);
    rreturn ~~~;
}
  • 이렇게 write쿼리를 날리는 부분과 select 쿼리를 날리는 부분을 분리하는것이 좋다는 얘기!

질문

내부변경이 일어나는것은 커맨드, 명령어라고 부른다
커맨드는 결과값을 그대로 반환하면안된다
내부변경이 일어나지않는 것은 쿼리라고 부른다.
쿼리는 결과값을 반환한다

-> 커맨드에서 결과값을 반환하면 안되는 이유가 뭘까요?

return값을 entity

  • 이때의 문제점이 무엇일까?
  • entity정보가 다 노출된다. 화면에 노출하고싶은것만 return해야하는데 그러지 않은 정보들도 노출이 된다.@JsonIgnore을 사용한다면 노출되는것을 막을 수 있지만 같은 엔티티인데 어쩔때는 내보내야하고 어쩔때는 내보내지 않아야하는 경우에는 어떻게 할것인가? 이런이유로 return값이나 parameter나 모두 entity로 받으면 안되고 dto로 받아야한다
  • @gitIngnore는 화면에 대한 entity이다 이 어노테이션을 추가한다는 것은 화면 관련된 로직이 들어간다는 것이다
  • 그리고 array를 바로 반환하면 count를 내놓으라고 할때 어떻게 해야할지를 모른다 예를들어서
    count: 2,
    data: []
    이런결과는 가능하겠지만
    count:2,
    [{},{},...]
    이런건 힘들거라는 얘기
    요구사항에 대해 유연하게 대처하지 못할거라는얘기

map

members.stream().map(m-> new MemberDto(m.getName))).collect(collectore.toList());

map을 사용해서 member list -> memberDto list로 변환하자

반환타입

@Data
@AllArgsConstructor
static class Result<T> {
private T data;
}

그리고 Result(memberDtoList)를 넣어주면 유연성을 확보할 수 있다.
-> 리스트를 반환하는게 아니라 객체를 반환하자

성능 최적화

  • 등록, 수정은 성능문제가 많이 발생하지 않음. 주로 조회에서 많이 발생함

샘플데이터만들기

  • postConstuctor: 클래스를 빈으로 등록해두고 이 어노테이션을 추가한 생성자에서 어떤 메서드를 호출하면 애플리케이션 구동시마다 선언한 메서드가 실행될것이고, 그 메서드에 샘플데이터 생성코드를 넣어주면 매번 샘플데이터가 애플리케이션 구동시마다 생성될것이다.
  • 자바에도 ... 문법이 있구나

xToOne 성능최적화

  • 이번에도 나는 entity를 그대로 list에 담아서 return 했다
    하지만 그 enttiy에는 양방향 연관관계가 맺어져있는 entity가 들어있었다. 그래서 순환참조 문제가 발생한다.
    이거 해결하려면 양방향 연관관계에서 한쪽을 JsonIgnore설정해주어야한다
    하지만 그래도 가지고 있는 멤버변수중 하나가 지연로딩인 경우에는 또 문제가 발생한다. 왜냐하면 지연로딩의 경우 해당 멤버변수에 대한 객체가 진짜로 들어있는게 아니라 프록시객체가 들어있기때문이다 json라이브러리는 그 프록시객체를 이해할수없다.
    에러를 뱉게 된다
    이문제를 해결하려면 특정한 모듈을 다운받아서 빈으로 등록해주어야한다. 그래서 이걸 아예출력하지 말라고 해야한다.
    물론 저걸 출력하게도 할 수 있다. 그런데 그럼또 모든 엔티티가 다 노출된다.
  • 그리고 성능상에도 문제가 된다. 필요없는것까지 더 가져오는거니까. 어마어마하게 쿼리가 나가게 될 것이다.
  • 그렇다고 이 문제에서 laze를 eager로 바꾸면 안된다 다른 api에서는 lazy여도 될수도있지않은가? 그때도 성능최적화를 할수없어지기 때문에 이또한 안된다
  • 그리고 디펜던시 추가할때 버전 안적어도 스프링부트가 알아서 웬만한것들은 적당한 버전을 가지고 와준다

v2: dto를 사용해보자

orders.stream().map(o->new OrderDto(o)).collect(Collectors.toList());

map을 사용하고 dto내부에 entity를 받아서 dto를 초기화하는 코드를 작성하자

v2의문제

  • 모두 lazy로딩이 되어있는데 map을 돌면서 lazy초기화가 진행된다. 그러니까 맨처음부터 join을해서 가져왔으면 한번의쿼리로 끝났을것을 지금 3개의 테이블이 필요하니까 3번의 쿼리가 더 날라간거다. 성능문제가있다.
  • select order를 해온다. 그리고 order를 매번 lazy초기화를 해준다. lazy초기화를할때마다 연관관계에있는 데이터들에 대한 쿼리가또 나간다. 쿼리가 너무많이 나가고 있다.

v3: fetch join사용

하지만 이 방법도 좋진 않다. 왜냐하면 entity의 모든정보를 다 끌고왔기때문. 필요한 정보만 가져와보자!

v4: dto로 결과를 조회하자

em.createQuery("select new jpabook.jpashop.repository.OrderSimpleQueryDto(o)...");

이렇게 Order o 를 넘기는건 안된다. 식별자가 넘어가기때문이다.
하나하나 변수를 다 넘겨주어야한다

트레이드오프

  • v4보다 v3가 재사용성이 높다
  • v3는 entity를 조회해온거여서 비즈니스로직에서 무언가 변경할 수있는데 v4는 그럴 수 없다. dto로 조회해온 것이기 때문에
  • v4는 코드가 지저분하다
  • v4의 성능이 v3보다 좋다
  • api spec이 바뀌면 v4도 바껴야함. 계층분리가 잘 안되어있음

결론

repository는 v3으로 쓰고 v4는 따로 디렉터리를 만들어서 거기에다 따로 작성하자


출처
https://www.inflearn.com/questions/47205/dto-inner-class-%EC%82%AC%EC%9A%A9-%EB%B0%A9%EC%8B%9D

profile
안녕하세요!
post-custom-banner

0개의 댓글