have a good time 2021. 12. 16. 17:49

 

 

사진 업로드 사이트에서 업로드 하면, 프로필 화면에 그 사진이 나타나도록 하겠다.

 

 

 

<프로필 화면> - 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프로젝트 - 포토그램 만들기"