[Spring] 11/2 팀프로젝트 구현(엔터티 설계, 마이페이지 작성)
엔터티 설계
테이블 설계를 맡아서 엔터티를 일괄적으로 만들고, 조장님이 베이스가 되는 basic.html 파일을 만들어 주시면 그 둘을 합쳐서 베이스 프로젝트로 해서 시작하기로 했다. 수업을 들을 때는 엔터티 설계를 우습게 봤다만.. 생각보다 오래걸리고 어려운 작업이었다. 처음부터 혼자 시작하려고 하니 수업때보다 훨씬 막막하고 어려웠다.
게시물 좋아요, 북마크, 채팅 참가자, 댓글 좋아요 이렇게 4개의 엔터티가 복합키를 가져서 방법을 찾아보니 복합키 자체를 하나의 클래스로 더 만들어줘야 해서 총 17개의 파일이 나왔다.
BoardLike를 예시로 작성해보자면
// BoardLike.java
// import 생략
// Annotation 생략
import jakarta.persistence.*;
public class BoardLike {
@EmbeddedId
private BoardLikeId id;
@MapsId("userId")
@ManyToOne
@JoinColumn(name = "user_id", nullable = false)
private User user;
@MapsId("boardId")
@ManyToOne
@JoinColumn(name = "board_Id", nullable = false)
private Board board;
// 나머지 필드 생략
}
package org.pgm.ootdproject.entity;
import jakarta.persistence.Embeddable;
import java.io.Serializable;
// BoardLike 테이블은 복합키를 가지므로 복합키를 하나의 클래스로 다시 만들어줌
@Embeddable
public class BoardLikeId implements Serializable {
private long boardId;
private long userId;
public BoardLikeId() {}
public BoardLikeId(long boardId, long userId) {
this.boardId = boardId;
this.userId = userId;
}
}
이런식으로 하나의 클래스로 엮은 뒤, 엔터티 클래스에서 @EmbeddedId, @MapsId annotation을 이용해 복합키를 가진 클래스를 만든다.
마이페이지 만들기
내가 맡은 기능은 마이페이지였다. 마이페이지는 아코디언 방식을 이용해서 회원정보 버튼을 누르면 프로필 조회, 프로필 수정 버튼이 나타나도록 구현하기로 했다. 세부적으로 살펴보면
-
회원정보
- 수정
- 탈퇴
-
내가 올린 게시물
한 페이지에서 내가 올린 게시물을 한꺼번에 볼 수 있도록 하고, 오른쪽에 삭제 버튼이 있어서 간편하게 지울 수 있는 방식으로 만들었다.
- 조회
- 삭제
-
댓글 조회
-
북마크
북마크도 내가 올린 게시물과 같은 방식으로 만들었다.
- 조회
- 수정
User
마이페이지 기능에서는 User, Reply, Board, Bookmark 이렇게 4개가 필요하고, 마이페이지의 기능에 맞춰서 CRUD 중 필요한 기능을 구현했다. 블로그에는 이 중 User만 예를 들어 작성을 하겠다.
Repository
package org.pgm.ootdproject.repository;
import org.pgm.ootdproject.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByLoginId(String loginId); // 로그인 id로 사용자를 찾아냄
}
ServiceImpl
인터페이스는 생략하겠다
package org.pgm.ootdproject.service;
import jakarta.transaction.Transactional;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.pgm.ootdproject.entity.User;
import org.pgm.ootdproject.repository.UserRepository;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
@Data
@Transactional
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
@Override
public User updateUser(Long userId, User updatedUser) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new RuntimeException("User not found"));
user.setNickname(updatedUser.getNickname());
user.setIntroduce(updatedUser.getIntroduce());
user.setProfileImage(updatedUser.getProfileImage());
user.setEmail(updatedUser.getEmail());
return userRepository.save(user);
}
@Override
public void deleteUser(Long userId) {
userRepository.deleteById(userId);
}
@Override
public Optional<User> readUser(Long userId) {
return userRepository.findById(userId);
}
}
Controller
package org.pgm.ootdproject.controller;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.pgm.ootdproject.entity.User;
import org.pgm.ootdproject.service.UserService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.Optional;
// 마이페이지에서 사용자 수정(update), 조회(show), 탈퇴(delete)
@Controller
//@RequestMapping("/my")
@RequiredArgsConstructor
@Log4j2
public class UserController {
private final UserService userService;
// 테스트
// @GetMapping("/test")
// public String testPage() {
// log.info("aaa");
// return "/mypage/test";
// }
// 마이페이지
@GetMapping("/mypage")
public String showMyPage(Model model) {
Long userId = 2L;
model.addAttribute("userId", userId);
return "/my/mypage";
}
// 프로필 조회
@GetMapping("/profile/{userId}")
public String readUserProfile(@PathVariable Long userId, Model model) {
// User user = userService.readUser(userId).orElse(null);
// model.addAttribute("user", user);
// return "/my/profile";
Optional<User> user = userService.readUser(userId);
user.ifPresent(value -> model.addAttribute("user", value));
return "/my/profile";
}
// 프로필 수정 폼
@GetMapping("/profile/{userId}/updateMyProfile")
public String updateProfileForm(@PathVariable Long userId, Model model) {
Optional<User> user = userService.readUser(userId);
user.ifPresent(value -> model.addAttribute("user", value));
return "/my/updateMyProfile";
}
// 프로필 수정 처리
@PostMapping("/profile/{userId}/updateMyProfile")
public String updateProfile(@PathVariable Long userId,
@ModelAttribute User updatedUser,
Model model) {
User user = userService.updateUser(userId, updatedUser);
model.addAttribute("user", user);
return "redirect:/profile/" + userId;
}
@GetMapping("/delete")
public String deleteUser(Long userId) {
log.info("delete");
userService.deleteUser(userId);
return "redirect:/profile";
}
}
작동 화면
마이페이지
다른 버튼들도 누르면 아래 항목들이 잘 나왔다.
마이페이지 => 프로필 조회
프로필 조회 => 수정
수정이 잘 된 것을 볼 수있다.
게시물 조회
나머지도 잘 출력됐지만, 북마크 조회에서 제목을 클릭하거나 삭제 버튼을 눌렀을 때는 아직 구현하지 못했다. 복합키때문에 조금 복잡해질 것 같아 오늘은 일단 마무리..
많이 했던 실수 & 오류
-
templates 폴더 위치..
이렇게 있는게 정상이다. 처음 배포받은 베이스 프로젝트에는 똑바로 돼있었지만 내가 드래그를 하면서 실수로 잘못 옮겼는지 static 폴더 안에 templates 폴더가 들어가 있었다. 이것때문에 최소 두시간 헤매다가 선생님과도 한시간 헤맸다가 선생님이 찾아주셔서 욕 한바가지 먹었다.. ㅋㅋㅋ
-
요청경로, 리턴받은 경로값, 리다이렉트
요청 경로와 반환하는 뷰의 경로가 헷갈리고 계속 보다보니 뇌에 과부하가 와서 계속 경로를 수정했다. 그리고 처음에 파일을 작성하기 전에 요청경로 같은걸 정리해놓고 해야겠다고 생각했다. 미리 안짜놓고 무작정 아무 경로나 집어넣다 보니까 계속 파일을 보면서 왔다갔다 하느라 시간을 낭비했다.
-
시간 출력
시간을 출력할 때 처음에 위의 방법으로 했지만, LocalDateTime 객체는 #temporals를 사용해 포맷해야 한다고 한다.
<td th:text="${#dates.format(board.regDate, 'yyyy-MM-dd HH:mm')}">작성 날짜</td>
<td th:text="${#temporals.format(board.regDate, 'yyyy-MM-dd HH:mm')}">작성 날짜</td>
댓글남기기