인스타그램 클론 프로젝트-(7)

Claudia Hong·2021년 8월 16일
0

Project

목록 보기
13/26
  1. 디폴트 프로필 페이지 이미지 렌더링

1) 로그인한 유저의 프로필페이지만 보이게 익셉션처리

21줄 : 프로필 페이지로 올 때 데이터를 들고 오도록 해야한다. >> 해당하는 UserController 에서 Model에 담아서 데이터를 들고 간다.
22줄 : UserService를 DI해서 userProfile()을 불러온다.

22줄 : userId >> {id}를 받아서 해당 유저의 모든 사진을 가져온다.
쿼리로 하면 SELECT * FROM image WHERE userId= :userId; 이렇다.

23줄 : 해당 유저를 찾지 못할 경우가 있으므로 orElseThrow()로 Optional을 해줘야 한다. 화살표 함수를 쓰고, 유효성검사가 아니기 때문에 별도의 CustomException을 만들어서(익셉션 내부의 errorMap은 지운다. 메세지만!) CustomException을 타게 한다. >> Handler에 등록도 해야함

2) 양방향 Mapping

회원 프로필 페이지로 이동을 할 때, 위에서처럼 이미지나 유저정보만 들고 가서 뿌려줄 수 있는게 아니다. Image(초록색), User(빨간색), Subscribe(파란색)...등 여러가지 정보들을 다 같이 들고 가야 한다.

이렇게 하면 유저 정보만 들고가는게 된다. 그래서 1번 유저의 정보를 SELECT 하면 DB에서 User를 영속성 컨텍스트로 들고오는데(영속화) 유저를 SELECT할때 관련된 Image들을 같이 뽑아오는 로직을 만든다. >> 양방향 매핑 (영속성컨텍스트>>JPA를 쓰면 있는 것)

.
User.java에 Image를 리스트로 해서 넣는다. 그런데 DB에서는 테이블에이미지들을 컬렉션으로 넣을 방도가 없으므로 DB에 넣지 않게 해야함.

49줄

  • 연관관계 >> 한개의 유저(One)는 여러개의 이미지들을(ToMany) 등록할 수 있다.
  • OneToMany의 fetch 옵션 : FetchType 디폴트 >> Lazy
    ① Lazy: User를 SELECT할 때, 해당 User id로 등록된 image들을 가져오지마. >> 대신 getImages() 함수의 image들이 호출될 때 SELECT해서 가져와.
    ② Eager: User를 SELECT할 때, 해당 User id로 등록된 image들을 전부 Join해서 가져와
  • mappedBy = "user" 에서 user는 클래스 Image의 user 변수.
  • mappedBy = "user" 의미
    ① 나는 연관관계의 주인이 아니다. 그러므로 테이블에 컬럼을 만들지마.
    ② User를 SELECT 할 때 해당 User id(31줄의 id를 말함)로 등록된 image들을 다 가져와

위처럼 하고 나면, 아래 UserService에서 SELECT까지는 (22~23줄) 유저만 가져오고 이미지들은 SELECT하지 않은 상태이다.

3) 이미지 렌더링하기

(1) JSP
유저엔티티만 컨트롤러를 통해 모델에 전달해 프로필 jsp페이지까지 들고가서 쓰기만 하면된다

66줄 : EL표현식(${ })에서 변수명을 적으면 get함수가 자동 호출된다. >> getImages() 함수가 호출되서 함수가 돈다(forEach) >>데이터 갯수에 따라 돈다. (예: 데이터가 2개면 2번 돌고 3개면 3번 돈다.)

68줄 : img src="${image.postImageUrl} 만 해주면 이미지의 저장된 이름만 가져와서 화면에 엑박이 뜬다. >> yml에 적은 upload 폴더 경로를 별도로 설정해야한다.

(2) 웹설정 파일

12줄 : yml에 적은 업로드 파일 경로를 uploadFolder에 담는다.
16줄 : addResourceHandler 불러오면 오버라이드 해서 만들어짐.
19줄 : 16줄의 registry에
20줄 : /upload/뫄뫄 이런식으로 된 경로일 때
21줄 : file:/// 하고 위에서 담은 경로를 넣는다.(file:///C:\workspace\springbootwork\upload) ※ /는 꼭 3개!
22줄 : 60초 X 10번 X 6번 >> 캐시기간이 1시간이라는 의미
23줄 : true >> 발동

잘 나옴
.
.
.

4) 참고사항 및 문제점

(1) 참고사항 - System.out.print~ 주의하기

오브젝트를 콘솔에 출력할 때 문제가 될 수 있어 User 부분을 출력되지 않게 함. (무한참조 에러 발생 우려 때문에)

무한참조 에러(무한으로 getter가 계속 호출되는 것)가 발생하기도 하는데 UserService 에서 sys.out을 했기 때문이다 >> 이유 : JPA를 사용할 때는 오브젝트를 sysout 하는 것을 조심해야한다. + 컨트롤러에서 무언가를 리턴할 때도 마찬가지...
확인만 하고 바로 주석처리를 할 것

(2) Open-in-view 이해

클라이언트가 어떤 요청을 하게 되면 톰캣이라는 성 안으로 들어가게 된다. 구조를 보면 톰캣 내부에는 스프링 컨테이너라는게 있고 디스패쳐라는게 있다. 디스패쳐는 요청이 들어왔을 때, 어떤 컨트롤러로 가야할지 정해주는 것. 컨트롤러 >> 서비스 호출 >> 레퍼지토리 호출 >> 영속성 컨텍스트를 가지는데 영속성 컨텍스트가 DB를 호출

1번 클라이언트로부터 요청이 들어오면 디스패쳐가 받아서 어떤 컨트롤러로 선택할지 잡는다. 2번 이때 세션이 만들어짐=열림(DB에 접근할 수 있는 세션) 그렇게 컨트롤러 >> 서비스 >> 레포지토리 >> 영속성 컨텍스트로 가는데 영속성컨텍스트에 해당 데이터가 있으면 바로 영속성 컨텍스트에서 응답. 없으면 영속성컨텍스트가 DB에 데이터를 요청해서 응답을 받은걸 다시 응답해줌.
레포지토리가 그렇게 받은 응답을 레포지토리 >> 서비스(비지니스 처리) >> 처리된 결과를 컨트롤러에게 주면 >> 컨트롤러는 데이터로 해줄지 파일로 해줄지(RestController/Controller) 결정해서 돌려줌 이때, 3번 서비스 >> 컨트롤러 가는 시점에서 세션은 종료 됨 이렇게 되면 Controller 단에서 FetchType=Lazy일 때, 유저 정보와 함께 이미지를 들고 오지 못해서 에러가 남(지연로딩). 그런데 EAGER면 종료 전에 조인해서 유저정보와 이미지를 바로 들고 오니깐 가능함.

Lazy로딩의 경우 컨트롤러에서 이미지 데이터를 DB에서 가져오려고 하는데 세션이 종료되어서 못 들어오게 막는다.

yml에서 open-in-view:true 의미 : view 단 까지 세션을 오픈한다. 세션 종료가 노란선 부분에서 이루어진다.>> Lazy 로딩이 가능해진다.

false 로 하면 세션이 서비스와 컨트롤러 사이에서 닫힌다. 그러므로 EAGER 일 때는 가능하지만 Lazy는 안된다.

(3) @Transactional의 중요성

서비스단에서 데이터베이스에 변형을 줄 때는 Transactional을 꼭 걸어놔줘야 한다. (습관처럼 걸기)

예) 송금 서비스

유저1(만원) >> 유저2(3만원) 에게 5천원을 전송할 때,

유저1의 금액 update () >> 오천원으로 변경 ①
유저2의 금액 update () >> 3만 5천원으로 변경 ②

update가 2번이 되어야 하는데 1번은 성공하고 2번은 실패한다면?? 돈 5천원이 사라짐!! 이렇게 2가지 로직이 하나의 송금서비스 >> 하나의 트랜잭션 = 일의 최소 단위

@Transactional 을 붙이면 둘 중 하나가 실패를 하게 되면 성공한 것도 포함해서 전원 Rollback을 시킨다. (원상복귀) 둘다 성공할 때만, commit 한다.

DB에 변형이 일어나는 일을 할 때는 항상 트랜잭션을 걸어야 한다!

유저네임을 변경하면 레퍼런스가 같은 영속성컨텍스트의 유저네임도 바뀐다. 영속성 컨텍스트는 서비스가 끝나는 시점에 변경된 오브젝트를 감지하고(감지하기 위해 연산이 계속 들어감) DB에 자동 flush 한다. (집어넣는다)=더티체킹

여기서 SELECT인데, @Transactional을 걸되, readOnly = true 하는 이유 ? 변경 감지 연산을 안하게 된다. 영속성 컨텍스트가 적게 일하게 한다.

(4) 양방향 무한참조 에러 (에러 발생!!)

오류메세지

Failure while trying to resolve exception [org.springframework.http.converter.HttpMessageNotWritableException]

회원정보 수정 validation 잘 했고 유저 정보를 가져와서 세션에 변경해주고 유저엔티티를 응답 >> 응답시에 userEntity의 모든 getter 함수가 호출되고 JSON으로 파싱하여 응답한다.

User.java에서 getImages()를 호출하게 된다. >> 이미지를 모두 호출 >> 또 유저 호출 >> 또 이미지 호출 >> 또 유저 호출......무한 참조 에러가 발생 이때 아래의 방법으로 해결 가능하다.

52줄 : JSON 파싱이 User.java 내부까지는 되더라도 Image.java까지는 하지 못하게 한다. Image.java 내부의 user를 무시한다. getter 생성이 되지 않는다.

5) 기타 설정

(1) 사진등록 및 구독하기 버튼 on&off

1번 유저로 로그인 했을 때, 2번 유저의 페이지로 가면 사진등록 버튼이 뜨면 안된다.

현재 페이지의 주인인지에 대한 데이터를 같이 들고 가야 한다. >> 현재 페이지의 주인인지를 담는 DTO를 만든다.

Controller에서 넘어가는 id는 현재 페이지의 유저아이디(로그인한 유저의 아이디가 아닐 수 있음)를 의미하는 pageUserId로 바꿔서 구분한다.
현재 로그인한 유저의 id도 넘겨줘야 하므로 PrincipalDetails도 넘겨준다.

UserService에서 넘어가는 userId도 현재페이지의 유저아이디이므로 컨트롤러와 동일하게 pageUserId로 바꿔서 구분한다. 그리고 비교를 위한 현재 로그인한 유저의 id도 불러와야 하므로 principalId를 받아온다.

25줄 : userProfileDto를 불러오고
31줄 : dto에 userEntity를 넣어준다.
32줄 : dto에 페이지오너의 정보를 넣어주는데 로그인한 유저아이디와 비교해서 결과를 true or false로 받는다.
34줄 : 그리고 그걸 담은 dto를 반환한다.

24줄 : 그럼 UserController에서는 UserProfileDto로 받아야 한다.
25줄 : 또한 모델에 dto를 넘겨야 한다.

그리고 JSP에서 ${dto.user.name} 이런식으로 값을 불러오도록 바꿔줘야 한다.

JSP에서 조건을 건다.

34~6줄 : dto에서 넘어오는 값이 true면 사진등록 버튼을 띄우고
37~9줄 : false면 구독하기 버튼을 띄운다.

※ view 페이지에 로직을 넣는 방법은 추천하지 않는다.

(2) 게시물 갯수 보이기
JSP 파일에서 ${dto.user.images.size()} 를 넣으면 간단하게 해결되긴 하지만 JSP에서 연산을 하게 하지 말고 데이터를 만들어서 가져가는 방법을 쓰는 것이 좋다. (프론트엔드 담당자가 페이지에서 연산을 하는걸 선호하지 않음.)

UserProfileDto에 int imageCount를 하나 더 만들고 Service에서 아래처럼 데이터를 만들어서 가져간다. JSP에서 ${dto.imageCount}로 부르면 됨.

※사진 사이즈가 2MB 초과했을 때 업로드 하면 오류페이지 뜸 >> 경고메세지 띄우는걸로 바꿀 것.

0개의 댓글

관련 채용 정보