Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
Tags
- JAVA기초
- 접근제어지시자
- break문
- continue문
- for문
- 반복문
- IntelliJ IDEA
- function
- 메서드
- 상수
- 자바 멀티스레딩
- While
- Thread
- OPP개념
- 포함관계
- 인텔리제이 한글 깨짐 해결법
- 연관관계
- Java
- 생성자
- 컴파일
- Java데이터 타입
- 인텔리제이 기초 설정
- 형 변환
- java변수
- this예약어
- 시스템 환경 변수 편집
- multi-threading
- JAVA객체지향
- 집합관계
- 메서드 오버로딩
Archives
- Today
- Total
최원종의 개발 블로그
스프링부트 V7 댓글 기능 추가 (User 도메인 리팩토링 ) 본문
UserService 리팩토링 진행 부분 정리
메서드 명명 규칙의 변화 : 기존의 영문 명칭(join, login)을
웹 화면의 역할과 일치하는 명확한 한글 네이밍(회원가입, 로그인)으로 변경
회원정보수정화면 분리 : 단순히 findById를 호출하던 것에서
'회원정보수정화면'이라는 비즈니스적 목적을 명확히 한 메서드로 개편
수정 로직 내부 의존성 제거 : 회원정보수정 내에서 내부 findById() 메서드를 호출하던 방식을 지우고,
repository.findById()를 직접 호출하여 단독 영속화 수행
로그 메시지 정돈 : 로직 사이에 불필요하게 흩어져 있던 완료 처리 로그를 정리하여
비즈니스 로직 본연의 가독성 향상
UserService 코드⬇️
더보기
package com.tenco.blog.user;
import com.tenco.blog._core.errors.Exception400;
import com.tenco.blog._core.errors.Exception404;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* User 관련 비즈니스 로직을 처리하는 Service 계층
* Controller 와 Repository 사이에서 실제 업무 로직을 담당
*/
@Slf4j
@Service // IoC
@RequiredArgsConstructor // DI
@Transactional(readOnly = true) // 기본적인 읽기 전용 트랜잭션 처리 , 조회시 더티 체킹 안 일어남
public class UserService {
private final UserRepository userRepository;
/**
* 회원 가입 처리
* @param joinDTO (사용자 회원가입 요청 정보)
* @return User (저장된 사용자 정보)
*/
@Transactional
public User 회원가입(UserRequest.JoinDTO joinDTO) {
log.info("회원가입 서비스 시작");
userRepository.findByUsername(joinDTO.getUsername()).ifPresent(user -> {
log.warn("회원가입 실패 - 중복된 사용자명 : {}", user.getUsername());
throw new Exception400("이미 존재하는 사용자 이름입니다");
});
User user = joinDTO.toEntity();
return userRepository.save(user);
}
/**
* 로그인 처리
* @param loginDTO (사용자가 요청한 로그인 정보)
* @return User(조회된 정보 세션 저장용)
*/
public User 로그인(UserRequest.LoginDTO loginDTO) {
log.info("로그인 서비스 시작");
User userEntity = userRepository.findByUsernameAndPassword(loginDTO.getUsername(), loginDTO.getPassword())
.orElseThrow(() -> {
log.warn("로그인 실패 - 사용자 이름 또는 사용자 비번 잘못 입력");
return new Exception400("사용자명 또는 비밀번호가 올바르지 않습니다");
});
return userEntity;
}
/**
* 사용자 정보 조회 (프로필 정보 보기 활용)
* @param id (User PK)
* @return UserEntity
*/
public User 회원정보수정화면(Integer id) {
log.info("사용자 정보 서비스 시작");
User userEntity = userRepository.findById(id).orElseThrow(() -> {
log.warn("사용자 정보 조회 실패");
return new Exception404("사용자 정보를 찾을 수 없습니다");
});
return userEntity;
}
/**
* 사용자 정보 수정 처리 (프로필 업데이트)
* @param id (User PK)
* @param updateDTO (사용자가 요청한 데이터)
* @return User
*/
@Transactional
public User 회원정보수정(Integer id, UserRequest.UpdateDTO updateDTO) {
log.info("회원정보 서비스 시작");
User userEntity = userRepository.findById(id).orElseThrow(
() -> new Exception404("사용자 정보를 찾을 수 없습니다"));
// 더티 체킹 활용
userEntity.update(updateDTO);
return userEntity;
}
}
UserController 리팩토링 정리
서비스 레이어 네이밍 동기화 : UserService의 한글 메서드 개편(회원가입, 로그인 등)에 맞춰 컨트롤러의 호출부를 완벽하게 매핑
변수명 가독성 및 일관성 향상 : 흩어져 있던 userEntity, user 등의 반환 변수명을
도메인 맥락에 맞게 'updateUser', 'user' 등으로 명확히 정돈
LoginDTO 파라미터명 명확화 : loginProc의 인자명을 loginDTO에서 'reqLoginDTO'로 변경하여
클라이언트로부터 들어온 '요청(Request)' 데이터임을 명시
단일 책임에 집중한 설계 유지 : 비즈니스 언어(용어)가 컨트롤러까지 관통하도록 리팩토링하여
전체 코드의 응집력과 결합도를 이상적으로 유지
UserController 코드 ⬇️
더보기
package com.tenco.blog.user;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@Slf4j
@Controller // IoC
@RequiredArgsConstructor // DI 처리
public class UserController {
private final UserService userService;
// 프로필 수정 기능 요청
@PostMapping("/user/update")
public String updateProc(UserRequest.UpdateDTO updateDTO, HttpSession session) {
updateDTO.validate();
User sessionUser = (User) session.getAttribute("sessionUser");
User updateUser = userService.회원정보수정(sessionUser.getId(), updateDTO);
session.setAttribute("sessionUser", updateUser);
return "redirect:/";
}
// 프로필 화면 요청
@GetMapping("/user/update-form")
public String updateFormPage(HttpSession session, Model model) {
User sessionUser = (User) session.getAttribute("sessionUser");
User user = userService.회원정보수정화면(sessionUser.getId());
model.addAttribute("user", user);
return "user/update-form";
}
// 로그인 화면 요청
// 주소 설계 - http://localhost:8080/login-form
@GetMapping("/login-form")
public String loginFormPage() {
return "user/login-form";
}
// 로그인 기능 요청
@PostMapping("/login")
public String loginProc(UserRequest.LoginDTO reqLoginDTO, HttpSession session) {
// 인증 검사 x, 유효성 검사 o
reqLoginDTO.validate();
User user = userService.로그인(reqLoginDTO);
session.setAttribute("sessionUser", user);
return "redirect:/";
}
// 로그아웃 기능 요청
@GetMapping("/logout")
public String logout(HttpSession session) {
// 세션 메모리에 내 정보를 없애 버림
session.invalidate();
return "redirect:/";
}
// 회원 가입 화면 요청
// 주소 설계 - http://localhost:8080/join-form
@GetMapping("/join-form")
public String joinFormPage() {
return "user/join-form";
}
// 회원 가입 기능 요청
// 주소 설계 - http://localhost:8080/join
@PostMapping("/join")
public String joinProc(UserRequest.JoinDTO joinDTO) {
// 인증검사 x, 유효성 검사 하기 o
joinDTO.validate();
userService.회원가입(joinDTO);
return "redirect:/login-form";
}
}
LoginInterceptor 요약
HandlerInterceptor 구현 : 스프링 MVC 프레임워크가 제공하는 인터셉터 규격을 상속받아
HTTP 요청을 가로채는 서블릿 필터 기능 구현
preHandle() 시점 컨트롤 : 컨트롤러(Handler)에 요청이 도달하기 전인 'preHandle' 단계에서
세션을 검사하는 선처리 로직 배치
인증 검증 및 예외 발생 : 세션 내 sessionUser 존재 여부를 판별하여,
비로그인 사용자일 경우 즉시 커스텀 예외(Exception401) 처리
@Component를 통한 싱글톤 IoC 관리 : 인터셉터를 스프링 컨테이너의 빈(Bean)으로 등록하여
메모리 효율성 및 관리 편의성 극대화
LoginInterceptor 코드⬇️

package com.tenco.blog._core.interceptor;
import com.tenco.blog._core.errors.Exception401;
import com.tenco.blog.user.User;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
@Component // IoC 대상 - 싱글톤 패턴
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
User sessionUser = (User) session.getAttribute("sessionUser");
if(sessionUser == null) {
throw new Exception401("로그인 먼저 해주세요");
}
return true;
}
}
UserResponse 코드(주석처리)⬇️
더보기
package com.tenco.blog.user;
public class UserResponse {
// @Data
// public static class JoinDTO {
// private Integer id;
// private String username;
// private String email;
//
// public JoinDTO(User user) {
// this.id = user.getId();
// this.username = user.getUsername();
// this.email = user.getEmail();
// }
// } // end of JoinDTO inner class
//
// @Data
// public static class SessionDTO extends User {
// // password 처럼 민감한 정보는 내려주지 않는다.
// public SessionDTO(User user) {
// super.setId(user.getId());
// super.setUsername(user.getUsername());
// super.setEmail(user.getEmail());
// }
// } // end of LoginDTO inner class
} // end of outer class
'Spring boot 입문' 카테고리의 다른 글
| V7-3 댓글 조회 기능 추가 (0) | 2026.05.21 |
|---|---|
| V7-2 댓글 작성하기 기능 추가 (0) | 2026.05.20 |
| V6-2 expose-session-attributes 의 false 사용 (0) | 2026.05.20 |
| 스프링부트 V6- OSIV (0) | 2026.05.20 |
| V5-2 서비스 계층 만들어 보기 (BoardService, UserService) + 리팩토링 (0) | 2026.05.20 |
