- 이번에는 저번 시간에 이어서 사용자가 저장한 이미지를 View 페이지에 뿌려 보여줄 수 있도록 구현해보려고 합니다.
- 여기서 양방향 매핑 개념이 나오게 됩니다.
📝 Entity
- 우선
User
객체에Image
객체를 저장하는 리스트를 새로 구현해주었습니다.
package com.cos.photogram.domain.user;
...
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class User {
...
@OneToMany(mappedBy = "user")
@JsonIgnoreProperties({"user"})
private List<Image> images = new ArrayList<>();
...
}
- 해당 변수는 연관관계의 주인이 아니므로 데이터베이스에 저장되지 않으며, 이후 JPA 무한 참조를 방지하기 위해
@JsonIgnoreProperties
어노테이션을 사용하였습니다. Image
객체는 아래와 같이 구현되어 있습니다.
package com.cos.photogram.domain.image;
...
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Image {
...
@JoinColumn(name = "user_id")
@ManyToOne(fetch = FetchType.LAZY)
private User user;
...
}
📝 Service
- 프로필 페이지를 출력하는 비즈니스 로직은 아래와 같습니다.
package com.cos.photogram.service;
...
@RequiredArgsConstructor
@Service
public class UserService {
...
@Transactional(readOnly = true)
public UserProfileDto profile(Long pageUserId, Long principalUserId) {
UserProfileDto userProfileDto = new UserProfileDto();
User userEntity = userRepository.findById(pageUserId).orElseThrow(() -> {
return new CustomException("프로필 페이지가 존재하지 않습니다.");
});
userProfileDto.setUser(userEntity);
userProfileDto.setPageOwnerState(pageUserId == principalUserId);
userProfileDto.setImageCount(userEntity.getImages().size());
return userProfileDto;
}
}
- 해당 함수는 출력하고자 하는 페이지의 사용자 식별자(
pageUserId
)와 현재 접속중인 사용자 식별자(principalUserId
)를 매개변수로 받아 해당 페이지의 주인인지 아닌지를 판별하여 DTO에 저장하고 있습니다. 이는 이후 View 페이지에서 분기를 통해 '사진등록' 버튼과 '구독하기' 버튼을 상황에 맞게 출력하기 위해서입니다. - 프로필 페이지 출력을 위한 DTO는 아래와 같이 구현하였습니다.
package com.cos.photogram.web.dto.user;
...
@Data
public class UserProfileDto {
private User user;
private boolean pageOwnerState;
private int imageCount;
}
- 예외 처리를 위해 사용한
CustomException
은 아래와 같이 구현되어 있습니다.
package com.cos.photogram.handler.ex;
public class CustomException extends RuntimeException {
//객체 구분에 사용
private static final long serialVersionUID = 1L;
public CustomException(String message) {
super(message);
}
}
- 새로운 Exception이 추가되었으니 마찬가지로 Exception Handler에 해당 Exception을 캐치하는 함수를 추가해주었습니다.
package com.cos.photogram.handler;
...
@RestController
@ControllerAdvice
public class ControllerExceptionHandler {
...
@ExceptionHandler(CustomException.class)
public String exception(CustomException e) {
return Script.back(e.getMessage());
}
}
📝 Controller
- 이제 컨트롤러에서 위에 구현한 서비스 로직을 호출합니다.
package com.cos.photogram.web;
...
@RequiredArgsConstructor
@Controller
public class UserController {
private final UserService userService;
@GetMapping("/user/{pageUserId}")
public String profile(@PathVariable Long pageUserId, Model model, @AuthenticationPrincipal PrincipalDetails principalDetails) {
UserProfileDto userProfileDto = userService.profile(pageUserId, principalDetails.getUser().getId());
model.addAttribute("dto", userProfileDto);
return "user/profile";
}
...
}
- 함수를 살펴보면 현재 접속중인 사용자와 요청한 페이지의 사용자 아이디를 매개변수로 전달하고 있으며
Model
객체에 반환된 DTO를 저장하여 View 페이지로 전달하고 있습니다. - 이후 View 페이지에서는 이를
dto
라는 이름으로 사용할 수 있습니다.
📝 WebMvcConfigurer
jsp
파일에서Model
객체를 사용하여 프로필 페이지를 출력해보기 전에 먼저 해야 할 설정이 있습니다.- 바로
WebMvcConfigurer
인터페이스를 구현하는 클래스를 생성하여 Web 설정 파일을 구현하는 것입니다. - 해당 클래스는 정적 리소스의 위치 경로를 미리 설정하여 이후 View 페이지에서 '/upload/**'과 같은 주소 패턴이 요청될 경우 해당 경로에서 리소스를 찾을 수 있도록 하는 역할을 수행할 것입니다.
package com.cos.photogram.config;
...
@Configuration
public class WebMvcConfig implements WebMvcConfigurer { // Web 설정 파일
@Value("${file.path}")
private String uploadFolder;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
WebMvcConfigurer.super.addResourceHandlers(registry);
registry
.addResourceHandler("/upload/**") //jsp 페이지에서 '/upload/**'와 같은 주소 패턴이 요청되면 실행
.addResourceLocations("file:///" + uploadFolder)
.setCachePeriod(60 * 10 * 6) //1시간
.resourceChain(true)
.addResolver(new PathResourceResolver());
}
}
addResourceHandler()
: 매핑 URL 설정addResourceLocations()
: 정적 리소스 위치 설정setCachePeriod()
: 리소스를 지정된 시간동안 브라우저에 캐쉬resourceChain()
: 해당 함수의 반환 타입은ResourceChainRegistration
으로 해당 클래스의addResolver()
함수를 통해 하나 이상의ResourceResolver
를 추가할 수 있음
📝 View
- 마지막으로 View 페이지에서는
Model
객체를 통해 받아온 데이터를 화면에 뿌리면 됩니다.
...
<!--유저정보 및 사진등록 구독하기-->
<div class="profile-right">
<div class="name-group">
<h2>${dto.user.name}</h2>
<c:choose>
<c:when test="${dto.pageOwnerState}">
<button class="cta" onclick="location.href='/image/upload'">사진등록</button>
</c:when>
<c:otherwise>
<button class="cta" onclick="toggleSubscribe(this)">구독하기</button>
</c:otherwise>
</c:choose>
<button class="modi" onclick="popup('.modal-info')">
<i class="fas fa-cog"></i>
</button>
</div>
<div class="subscribe">
<ul>
<li><a href=""> 게시물<span>${dto.imageCount}</span>
</a></li>
<li><a href="javascript:subscribeInfoModalOpen();"> 구독정보<span>2</span>
</a></li>
</ul>
</div>
<div class="state">
<h4>${dto.user.bio}</h4>
<h4>${dto.user.website}</h4>
</div>
</div>
<!--유저정보 및 사진등록 구독하기-->
</div>
</section>
<!--게시물컨섹션-->
<section id="tab-content">
<!--게시물컨컨테이너-->
<div class="profileContainer">
<!--그냥 감싸는 div (지우면이미지커짐)-->
<div id="tab-1-content" class="tab-content-item show">
<!--게시물컨 그리드배열-->
<div class="tab-1-content-inner">
<!--아이템들-->
<c:forEach var="image" items="${dto.user.images}">
<div class="img-box">
<a href=""> <img src="/upload/${image.post_image_url}" />
</a>
<div class="comment">
<a href="#" class=""> <i class="fas fa-heart"></i><span>0</span>
</a>
</div>
</div>
</c:forEach>
<!--아이템들end-->
</div>
</div>
</div>
</section>
...
📝 Result
'🚗 Backend Toy Project > Photogram' 카테고리의 다른 글
[Photogram] 구독하기 - 기능 구현 (0) | 2022.07.12 |
---|---|
[Photogram] 구독하기 - 뷰 렌더링 (0) | 2022.07.12 |
[Photogram] 프로필 페이지 - DB에 이미지 업로드 (0) | 2022.07.11 |
[Photogram] 프로필 페이지 - 서버에 이미지 업로드 (0) | 2022.07.10 |
[Photogram] 구독하기 (0) | 2022.07.10 |