프로필 페이지 4
사진 업로드 사이트에서 업로드 하면, 프로필 화면에 그 사진이 나타나도록 하겠다.
<프로필 화면> - profile.jsp 파일
UserController.java 파일에서
profil 메서드가
return "user/profile"; 할 때
Model 객체에 이미지들을 담아서, 같이 리턴해주면 된다.
요청 주소를 /user/{id} 로 해서
/user/1 이런식으로 오면, 1번 유저의 프로필 화면이(profile.jsp)
/user/2 이런 식으로 오면, 2번 유저의 프로필 화면(profile.jsp ) 이 나오도록 하겠다.
<UserController.java> 파일
@GetMapping("/user/{id}")
public String profile(@PathVariable int id, Model model) {
User userEntity = userService.회원프로필(id);
model.addAttribute("user", userEntity);
return "user/profile";
}
--------------------------
UserController.java 파일에서 UserService.java 의 회원프로필 메서드를 요청해서 프로필 정보를 가져온다.
<UserService.java > 파일
@Transactional
public User 회원프로필(int userId) {
User userEntity = userRepository.findById(userId).orElseThrow(()-> {
throw new CustomException("해당 프로필 페이지는 없는 페이지입니다.");
});
return userEntity;
}
만약 없는 사용자의 프로필 페이지를 요청하면, 에러메세지를 보여주도록 처리
3번 사용자가 데이터베이스에 없는데,
3번 사용자의 프로필 화면을 요청 ( /user/3 이런식으로 주소 요청)하면 아래와 같은 에러메세지 나옴
하지만, /user/1 이런 식으로
데이터베이스에 있는 1번 유저 번호로 주소를 보내면
프로필 화면으로 잘 이동한다.
이러한 에러 처리 위해 CustomException.java 파일 만듦
public class CustomException extends RuntimeException{
private static final long serialVersionUID = 1L;
public CustomException(String message) {
super(message);
}
}
ControllerExceptionHandler.java 파일에 CustomException을 낚아채도록 아래와 같이 만듦
@ExceptionHandler(CustomException.class)
public String exception(CustomException e) {
return Script.back(e.getMessage());
}
즉, CustomException 이 요청되면, ControllerExceptionHandler.java 에 처리해준대로, 아래 코드가 실행되서 리턴됨
return Script.back(e.getMessage());
----------------------------------------------------------------------------------------------------------------
그런데, 프로필 화면으로 이동 시, 업로드한 이미지(image 정보) 뿐만 아니라
게시물 수, 프로필 사진 등 user 관련 여러 데이터, 구독 정보(subscribe 정보)를 같이 들고 가야 하므로,
user 데이터를 데이터베이스로부터 select 해서 영속성 컨텍스트에 넣을 때,
image 정보도 같이 가져올 수 있도록 양방향 매핑 사용
기존에, Image.java 파일에, 업로드한 유저 정보도 같이 넣기 위해,
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
@Entity
public class Image {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String caption;
private String postImageUrl;
@JoinColumn(name = "userId")
@ManyToOne(fetch = FetchType.EAGER)
private User user;
private LocalDateTime createDate;
@PrePersist
public void createDate() {
this.createDate = LocalDateTime.now();
}
}
이 처럼 user 변수도 같이 넣었다.
User.java 파일에서는 원래 image 변수를 사용하지 않았는데, 양방향 매핑을 위해 image 변수 추가
<User.java> 파일
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@Column(length = 100, unique = true)
private String username;
@Column(nullable = false)
private String password;
@Column(nullable = false)
private String name;
private String website;
private String bio;
@Column(nullable = false)
private String email;
private String phone;
private String gender;
private String role;
private String profileImageUrl;
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Image> images;
private LocalDateTime createDate;
@PrePersist
public void createDate() {
this.createDate = LocalDateTime.now();
}
}
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
<1> @OneToMany : 한 명의 유저는 여러 개의 이미지 만듦
mappedBy = "user" : Image.java 파일에서 User 객체 변수 이름, 즉 user 로 아래와 같이 사용했는데, 이 값을 넣어줘야 함
private User user;
<2> mappedBy 뜻 :
1) 나는 연관관계의 주인이 아니다. 그러니 테이블(데이터베이스)에 칼럼 만들지 마라.
(특히, images 변수가 List 타입이다. 그런데 데이터베이스에는 collection 타입이 없어서 이를 만들 수 없다.)
2) User 를 select 할 때 해당 User id 로 등록된 image 들을 다 가져와(그런데 이 때, 위에서 fetch 타입에 따라 다양한 결과)
즉, 연관관계의 주인은, image 테이블의 user 변수임.
private User user;
<3> fetch = FetchType.LAZY
타입 LAZY : User 를 select 할 때 해당 User id 로 등록된 image들을 가져오지마라. 단, getImages() 함수 image들이 호출될 때 가져오기
타입 EAGER : User 를 select 할 때 해당 User id 로 등록된 image 들을 전부 join 해서 가져오기
LAZY 와 EAGER 전략에 대해 살펴보겠다.
1) LAZY 전략
UserService.java 에서 아래와 같이 user를 select 할 떄, images를 가져오지 않음
User userEntity = userRepository.findById(userId).orElseThrow(()-> {
throw new CustomException("해당 프로필 페이지는 없는 페이지입니다.");
즉, 이 UserService.java 코드는, 사용자가 /user/{id}로 요청할 때 실행되는 코드이므로,
사이트에서 /user/1 이렇게 요청을 해보고 sts(이클립스) 콘솔창에서 보면
데이터베이스 쿼리를 볼 수 있는데,
이런 식으로 쿼리가 나타나면서 맨 마지막에 보면
이런식으로, user 테이블에서만 select 했음을 알 수 있다.
2) EAGER 전략
그런데 만약 아래와 같이 EAGER 전략으로 바꾼뒤
@OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
private List<Image> images;
사이트에서 /user/1 이렇게 요청을 하면
이런식으로, image와 user가 join 되서 같이 가져왔음을 알 수 있다.
3) LAZY 전략
그러면 이제 다시, LAZY 전략으로 바꾼 다음 언제 images 정보를 가져오는지 살펴보겠다.
@Transactional
public User 회원프로필(int userId) {
User userEntity = userRepository.findById(userId).orElseThrow(()-> {
throw new CustomException("해당 프로필 페이지는 없는 페이지입니다.");
});
System.out.println("================");
userEntity.getImages().get(0);
return userEntity;
}
============== 를 기준으로
위에에서는 user 데이터를 데이터베이스로부터 불러오고
아래에서는 user 데이터에 있는 image 데이터를 불러온다.
그러면 sts(이클립스) 콘솔창에서 보면
이런식으로 =======를 기준으로
위에서는 user 데이터만 가져오고(LAZY 전략을 사용했기 때문에, getImages() 를 사용하지 않는 한, user 데이터를 select 하면 user 데이터만 가져옴)
아래에서는 images 데이터를 가져온 것을 알 수 있다. (getImages() 를 사용했기 때문에, user 테이블과 양방향 매핑하고 있는 images 데이터도 가져옴)
위에 나타난 쿼리의 끝까지 따라가보면, ========= 윗 부분에서는 아래와 같이 user 테이블로부터 데이터를 가져오고

=========== 아래에서는 아래와 같이 image 테이블로부터 데이터를 가지고 왔음을 알 수 있다.

여기서 get(0) 을 한 이유는,
첫번째 데이터까지 가져오라는 뜻.
getImages() 만 하는 것은, 객체를 호출하는 거임.
이처럼 양방향 매핑은,
controller 의 Model 객체로 jsp 파일에 데이터 전달 시
user 정보만 전달하더라도 image 정보까지 같이 전달할 수 있게 됨
참고 자료 : 이지업 강의 사이트 "스프링부트 SNS프로젝트 - 포토그램 만들기"