Image 엔티티를 설계하고. Image 객체를 도입하였다.
ImageService 에서 Image Form 태그로 받은 이미지에 UUID를 붙여 Upload 폴더에 저장하고, imageRepository로 DB에 Image 객체를 저장하였다.
즉 사용자의 사진 업로드를 구현하였다.
이제 프로필 페이지를 살펴보자
1번 사용자로 로그인하여 프로필 화면으로 들어가면, User, Image, Subscribe 정보들이 모두 필요하다.
즉 user/{userId} 로 이동할 때 Model 에 User,Image,Subscribe 들을 넘겨주어야 한다.
이를 바탕으로 프로필 페이지를 구현해보자.
1 : N 관계에서는 N이 FK를 갖는다.
유저 : 이미지 = 1 : N 이기에, 이미지는 UserId를 FK로 갖고있다.
그런데 우리는 유저 프로필에서 User를 조회할 때, 해당 User가 올린 이미지들을 함께 가져오고 싶다.
따라서 유저에도 Image들을 갖고있게 하려하는데, 이를 양방향 매핑이라 한다.
UserController이다.
/user/{id} 즉 유저프로필 페이지로 이동 시 userService의 회원프로필(id)를 호출하여 모델에 담아 뷰로 이동한다.
UserService이다.
findById로 해당 User가 존재하면 User 객체를 반환하고, 존재하지 않으면 예외를 던진다.
User 클래스이다.
양방향 매핑으로 User에도 Image 리스트를 갖게하였다.
이 때 1 : N 이므로 @OneToMany를 추가했다.
근데 @Entity를 추가해놓은 상태기에 이대로면 DB의 컬럼으로 추가하게 된다. 그런데 1:N에서 1에는 FK가 없기에 이를 방지해야 한다.
따라서 mappedBy 로 Image의 FK와 연동시키고, fetch 로 가져오는 조건을 설정한다.
LAZY 면 User를 Select 후 getImage를 해야만 자동으로 쿼리문을 실행 해 해당 User의 Image들을 모두 저장한다.
Images 를 담은 User을 Model에 담아 뷰로 넘겨주었다.
이제 View에서는 표현식으로 변수들을 사용하면 된다.
이름 등을 출력하도록 변경해주고
Image를 출력하는 부분이다.
양방향 매핑에서 fetch가 LAZY이기에 getImage 를 해야 Join문으로 Image들을 가져와 리스트에 저장하는데, EL표현식에서는 자동으로 get함수가 호출된다.
img src는 절대경로를 입력해주어야한다. 이때 디렉토리 경로 입력도 필요한데.
Configuration 을 정의하여, JSP에서 /upload/** 이 존재하면 앞에 application.yml에 선언한 디렉토리 path를 추가해주도록 하였다.
View에서 사용자 정보와 이미지가 제대로 출력됨을 확인할 수 있다.
그런데 사진을 업로드하면 에러가 발생한다. 그 이유가 무엇일까?
ImageService에서 보면 사용자의 image를 저장한 후 출력하게하였다.
그러면 toString이 호출되어 Image의 모든 필드가 출력되게된다.
이때 Image의 필드에 User이 존재하므로 User도 toString을 호출하게 되는데..
User에는 양방향 매핑으로 Images를 갖고있다.
따라서 다시 Image를 참조하며, 서로 참조해 무한참조가 일어나 에러가 발생하게된다.
Image 객체를 콘솔에서 출력하려면, toString 오버라이딩 후 User를 출력하는 부분을 빼서 무한참조를 방지해야 한다.
update를 보면 User를 리턴한다.
이때 User의 모든 필드를 get하여 JSON화 해서 반환하는데, 또 무한참조가 생기게 된다.
따라서 User의 Images 필드에 @JsonIgnoreProperties를 추가해, get 후 JSON화를 방지해야한다.
양방향 매핑을 사용할 때는 get함수와 toString 함수에서 무한참조를 유의하자.
클라이언트가 요청을 하면, 디스패쳐 서블릿이 적절한 Controller를 호출한다. 이때 DB에 접근이 가능한 세션이 생긴다. 즉 DB에 Select가 가능해짐.
Repository가 DB에 Select 를 수행하면, 영속성 컨텍스트에 저장되어 활용한다.
Service가 비즈니스로직을 수행한 후 Controller에게 돌려주고 나면 DB접근 세션이 종료된다.
이때 우리는 User : Image 에서 fetch 를 LAZY로 매핑하였다. 즉 User.getImages를 해야 땡겨오는데, 이건 최종 컨트롤러 단에서 일어난다.
그 순간 이미 DB 접근 세션이 종료되었기에, DB에 접근해 조인문으로 Image 들을 땡겨오지 못하고 에러가 발생할 것이다.
즉 fetch를 LAZY로 하여 매핑하면, get함수가 호출될 때 Join문으로 땡겨온다. 그런데 최종 Controller 단에서 get함수가 호출되므로 이때는 DB접근 세션이 없어 Join문을 호출하면 에러가 발생한다.
그래서 application.yml 에 jpa 설정을 보면 open-in-view 가 있다.
이를 true로 하면 DB 접근 세션을 최종 Controller에서도 사용할 수 있도록 해준다.
제대로 컨트롤러에서 Join문을 활용한 Select문으로 Image들을 땡겨오며 뷰를 보내는 것을 확인할 수 있다.
open-in-view를 true하면 DB접근 세션을 최종 Controller 까지 연장해준다. 따라서 LAZY 방식 후 최종 Controller에서 get함수를 호출해도, DB 접근 권한이 있기에 수행 가능하다. open-in-view를 false로 할 것이라면 EAGER로 해서 미리 영속성 컨텍스트에 조인결과를 보관하면 된다.
Repository 가 DB에서 Select 를 수행하면, 이를 영속성 컨텍스트에 저장해 활용한다.
이 때 Service에서 영속성 컨텍스트 객체에 변화가 생기면, 이를 자동으로 DB에 Flush한다.
따라서 영속성 컨텍스트는 Select 된 객체에 변화가 생기나 수시로 확인하고 있다.
profile.jsp 에서 images 리스트의 크기를 출력하도록하면 됨
1번으로 로그인 후 2번페이지로 와도 사진등록이 가능하다.
다른 계정의 프로필페이지면 구독하기가, 자신 계정 페이지면 사진등록이 표시되도록 하자
profile.jsp 에서 JSTL로 if문을 생성해서, 세션 유저와 페이지 유저가 같은지 비교해서 출력하게 하면 되지 않을까?
-> 뷰 페이지가 복잡해진다.
Dto 에 담아 전송하자
UserProfileDto 생성
userService 의 회원프로필 메서드에서 userProfileDto에 User, PageOwner, imageCount 등을 담아서 컨트롤러에게 넘김
컨트롤러는 dto를 모델에 담아 뷰에게 전달
profile.jsp에서 dto를 활용하여 뷰를 렌더링.
뷰에서 하는 일을 줄이고, 컨트롤러에서 처리하게함.