
Controller
@PostMapping("/registration")
public ResponseEntity<UserCreateResponseDto> createUser(@Valid @RequestBody UserCreateRequestDto ucrDto) {
return ResponseEntity
.status(HttpStatus.CREATED)
.body(userService.createUser(ucrDto));
}
================================================================================================
Service
public UserCreateResponseDto createUser(UserCreateRequestDto ucrDto) {
if (userRepository.existsByEmail(ucrDto.getEmail())) {
throw new CustomException(ALREADY_EMAIL_USER);
}
if (userRepository.existsByUserName(ucrDto.getUserName())) {
throw new CustomException(ALREADY_USERNAME_USER);
}
User user = UserCommand.Create.toEntity(ucrDto, passwordEncoder, ADMIN_KEY);
userRepository.save(user);
return userMapper.userToUserCreateResponseDto(user);
}
Controller
@PostMapping("/registration")
public ResponseEntity<UserCreateResponseDto> createUser(@Valid @RequestBody UserCreateRequestDto ucrDto, HttpServletResponse res) {
UserCreateResponseDto resDto = userService.createUser(ucrDto);
jwtUtil.addJwtToCookie(resDto.getToken(), res);
return ResponseEntity
.status(HttpStatus.CREATED)
.body(resDto);
}
================================================================================================
Service
public UserCreateResponseDto createUser(UserCreateRequestDto ucrDto) {
if (userRepository.existsByEmail(ucrDto.getEmail())) {
throw new CustomException(ALREADY_EMAIL_USER);
}
if (userRepository.existsByUserName(ucrDto.getUserName())) {
throw new CustomException(ALREADY_USERNAME_USER);
}
User user = UserCommand.Create.toEntity(ucrDto, passwordEncoder, ADMIN_KEY);
userRepository.save(user);
String token = jwtUtil.createToken(user.getId(), user.getRole());
UserCreateResponseDto resDto = userMapper.userToUserCreateResponseDto(user);
resDto.setToken(token);
return resDto;
}


@PostMapping("/login")
public ResponseEntity<UserLoginResponseDto> logIn(@Valid @RequestBody UserLoginRequestDto ulrDto, HttpServletResponse res) {
UserLoginResponseDto resDto = userService.logIn(ulrDto);
String token = jwtUtil.createToken(resDto.getId(), resDto.getRole());
jwtUtil.addJwtToCookie(token, res);
resDto.setToken(token);
return ResponseEntity.status(HttpStatus.OK).body(resDto);
}
Controller
@PostMapping("/login")
public ResponseEntity<UserLoginResponseDto> logIn(@Valid @RequestBody UserLoginRequestDto ulrDto, HttpServletResponse res) {
UserLoginResponseDto resDto = userService.logIn(ulrDto);
jwtUtil.addJwtToCookie(resDto.getToken(), res);
return ResponseEntity.status(HttpStatus.OK).body(resDto);
}
================================================================================================
Service
public UserLoginResponseDto logIn(UserLoginRequestDto ulrDto) {
User user = userRepository.findByEmail(ulrDto.getEmail()).orElseThrow(() -> new CustomException(LOGIN_FAILED));
if (!user.isValidPassword(ulrDto.getPassWord(), passwordEncoder)) {
throw new CustomException(LOGIN_FAILED);
}
String token = jwtUtil.createToken(user.getId(), user.getRole());
UserLoginResponseDto resDto = userMapper.userToUserLoginResponseDto(user);
resDto.setToken(token);
return resDto;
}

Service
public ScheduleCreateResponseDto createSchedule(User user, ScheduleCreateRequestDto scrDto) {
Schedule schedule = ScheduleCommand.Create.toSchedule(scrDto, user, weatherService);
scheduleRepository.save(schedule);
UserSchedule userSchedule = ScheduleCommand.Create.toUserSchedule(schedule);
userScheduleRepository.save(userSchedule);
return scheduleMapper.scheduleToScheduleCreateResponseDto(schedule);
}
================================================================================================
Command
public static class Create {
private static String title;
private static String scheduleDetails;
private static String weather;
public static Schedule toSchedule(ScheduleCreateRequestDto scrDto, User user, WeatherService weatherService) {
title = scrDto.getTitle();
scheduleDetails = scrDto.getScheduleDetails();
weather = weatherService.getWeather();
return Schedule.builder()
.user(user)
.title(title)
.scheduleDetails(scheduleDetails)
.weather(weather)
.build();
}
객체 자체를 받아와서 Command 단에서 날씨를 받아 적용하고 있음Service
public ScheduleCreateResponseDto createSchedule(User user, ScheduleCreateRequestDto scrDto) {
Schedule schedule = ScheduleCommand.Create.toSchedule(scrDto, user, weatherService.getWeather());
scheduleRepository.save(schedule);
UserSchedule userSchedule = ScheduleCommand.Create.toUserSchedule(schedule);
userScheduleRepository.save(userSchedule);
return scheduleMapper.scheduleToScheduleCreateResponseDto(schedule);
}
================================================================================================
Command
public static class Create {
private static String title;
private static String scheduleDetails;
public static Schedule toSchedule(ScheduleCreateRequestDto scrDto, User user, String weather) {
title = scrDto.getTitle();
scheduleDetails = scrDto.getScheduleDetails();
return Schedule.builder()
.user(user)
.title(title)
.scheduleDetails(scheduleDetails)
.weather(weather)
.build();
}
알고 있어야 하는 의존성을 끊어낼 수 있었다. 앞으로도 더 디테일하게 코드를 작성하도록 노력이 필요할것 같다.





MapStruct 의 경우 많이들 사용하는것이 아니다 보니 설명이 필요할거라 생각치 못했다. (자세한 설명은 (링크))
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class UserCreateRequestDto {
@NotBlank(message = "닉네임이 비어있습니다.")
@Size(min = 3, max = 20, message = "닉네임은 3 ~ 20 글자사이로 입력해주세요.")
private String userName;
@NotBlank(message = "이메일이 비어있습니다.")
@Email(message = "입력된 이메일의 형태가 올바르지 않습니다.")
private String email;
@NotBlank(message = "비밀번호가 비어있습니다.")
@Size(min = 3, max = 20, message = "비밀번호는 3 ~ 20 글자사이로 입력해주세요.")
private String passWord;
private String adminKey;
}
@NotBlank 를 통해 빈값 혹은 공백이 오지 못하도록 적용.@Email 을 통해 Email 형태만 올수 있도록 구현. 더 정교한 검증이 필요하다면 @Pattern 사용을 고려해볼 듯.^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$@Pattern 을통해 검증가능@Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[!@#$%^&*()_+={}:;\"'<>,.?/\\[\\]\\\\-])[A-Za-z\\d~!@#$%^&*()+|=]+$"
{
"status": "BAD_REQUEST",
"message": "일정의 상세한 내용을 입력해주세요."
}


@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler({CustomException.class})
protected ResponseEntity<ErrorDto> handleCustomException(CustomException e, HttpServletRequest req) {
log.error("url:{}, trace:{}", req.getRequestURI(), e.getStackTrace());
return new ResponseEntity<>(new ErrorDto(e.getErrorCode().getCode(), e.getErrorCode().getDescription(),e.getErrorCode().getErrorCode()), HttpStatus.valueOf(e.getErrorCode().getCode().value()));
}
@ExceptionHandler({MethodArgumentNotValidException.class})
protected ResponseEntity<ErrorDto> handleValidationException(MethodArgumentNotValidException e, HttpServletRequest req) {
log.error("url:{}, trace:{}", req.getRequestURI(), e.getStackTrace());
return new ResponseEntity<>(new ErrorDto((HttpStatus) e.getStatusCode(), Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage(),"ERROR 001"), HttpStatus.valueOf(e.getStatusCode().value()));
}
}
ErrorDto 역시 동일한 필드를 가질수 있도록 설정GlobalExceptionHandler 또한 해당 정해진 ErrorCode를 가져오도록 변경 및 MethodArgumentNotValidException 의 경우 값입력에 대한 @Valid 이기에 에러코드를 001 로 고정하였음.ResponseEntity 에서도 설정이 가능하기에 생략도 고려해볼만 할 것 같다.


@Transactional
public SignupResponse signup(SignupRequest signupRequest) {
String encodedPassword = passwordEncoder.encode(signupRequest.getPassword());
UserRole userRole = UserRole.of(signupRequest.getUserRole());
if (userRepository.existsByEmail(signupRequest.getEmail())) {
throw new InvalidRequestException("이미 존재하는 이메일입니다.");
}
User newUser = new User(
signupRequest.getEmail(),
encodedPassword,
userRole
);
User savedUser = userRepository.save(newUser);
String bearerToken = jwtUtil.createToken(savedUser.getId(), savedUser.getEmail(), userRole);
return new SignupResponse(bearerToken);
}
@Transactional
public SignupResponse signup(SignupRequest signupRequest) {
if (userRepository.existsByEmail(signupRequest.getEmail())) {
throw new InvalidRequestException("이미 존재하는 이메일입니다.");
}
String encodedPassword = passwordEncoder.encode(signupRequest.getPassword());
UserRole userRole = UserRole.of(signupRequest.getUserRole());
User newUser = new User(signupRequest.getEmail(), encodedPassword, userRole);
User savedUser = userRepository.save(newUser);
String bearerToken = jwtUtil.createToken(savedUser.getId(), savedUser.getEmail(), userRole);
return new SignupResponse(bearerToken);
}
passwordEncoder 와 UserRole 을 먼저 세팅해두지 않고 Email을 먼저 비교하면 되는 간단한 문제.
public String getTodayWeather() {
ResponseEntity<WeatherDto[]> responseEntity =
restTemplate.getForEntity(buildWeatherApiUri(), WeatherDto[].class);
WeatherDto[] weatherArray = responseEntity.getBody();
if (!HttpStatus.OK.equals(responseEntity.getStatusCode())) {
throw new ServerException("날씨 데이터를 가져오는데 실패했습니다. 상태 코드: " + responseEntity.getStatusCode());
} else {
if (weatherArray == null || weatherArray.length == 0) {
throw new ServerException("날씨 데이터가 없습니다.");
}
}
String today = getCurrentDate();
for (WeatherDto weatherDto : weatherArray) {
if (today.equals(weatherDto.getDate())) {
return weatherDto.getWeather();
}
}
throw new ServerException("오늘에 해당하는 날씨 데이터를 찾을 수 없습니다.");
}
public String getTodayWeather() {
ResponseEntity<WeatherDto[]> responseEntity =
restTemplate.getForEntity(buildWeatherApiUri(), WeatherDto[].class);
WeatherDto[] weatherArray = responseEntity.getBody();
if (!HttpStatus.OK.equals(responseEntity.getStatusCode())) {
throw new ServerException("날씨 데이터를 가져오는데 실패했습니다. 상태 코드: " + responseEntity.getStatusCode());
}
if (weatherArray == null || weatherArray.length == 0) {
throw new ServerException("날씨 데이터가 없습니다.");
}
String today = getCurrentDate();
for (WeatherDto weatherDto : weatherArray) {
if (today.equals(weatherDto.getDate())) {
return weatherDto.getWeather();
}
}
throw new ServerException("오늘에 해당하는 날씨 데이터를 찾을 수 없습니다.");
}


같다면 주석 자체도 하나의 관리요소가 됄 수 있고, 자신의 코드가 자신이 없고 설명적이지 못하다는 반증이라는 말을 듣고 주석을 최대한 적게 쓰려고 노력중 이다.
google-java-format 를 사용하기로 결정 하였다.


참조 :
https://arthur.tistory.com/43
https://bbubbush.tistory.com/29

Snake Naming 로 작업할때가 있긴한데 최대한 고쳐갈 예정..
참조 : https://ko-ko.tistory.com/13

public void updateSchedule(Long scheduleId, ScheduleUpdateRequestDto surDto, User user) {
if (!user.isAdmin()) {
throw new CustomException(NOT_ADMIN);
}
Schedule schedule = scheduleRepository.findById(scheduleId).orElseThrow(() -> new CustomException(SCHEDULE_NOT_FOUND));
ScheduleCommand.Update.executeUpdate(schedule, surDto);
}
public void deleteSchedule(Long scheduleId, User user) {
if (!user.isAdmin()) {
throw new CustomException(NOT_ADMIN);
}
Schedule schedule = scheduleRepository.findById(scheduleId).orElseThrow(() -> new CustomException(SCHEDULE_NOT_FOUND));
scheduleRepository.delete(schedule);
}
public void updateSchedule(Long scheduleId, ScheduleUpdateRequestDto surDto, User user) {
Schedule schedule = getValidatedAdminSchedule(scheduleId, user);
ScheduleCommand.Update.executeUpdate(schedule, surDto);
}
public void deleteSchedule(Long scheduleId, User user) {
Schedule schedule = getValidatedAdminSchedule(scheduleId, user);
scheduleRepository.delete(schedule);
}
private Schedule getValidatedAdminSchedule(Long scheduleId, User user) {
user.isAdmin();
return scheduleRepository.findById(scheduleId).orElseThrow(() -> new CustomException(SCHEDULE_NOT_FOUND));
}
user Entity에게 위임. public UserCreateResponseDto createUser(UserCreateRequestDto ucrDto) {
if (userRepository.existsByEmail(ucrDto.getEmail())) {
throw new CustomException(ALREADY_EMAIL_USER);
}
if (userRepository.existsByUserName(ucrDto.getUserName())) {
throw new CustomException(ALREADY_USERNAME_USER);
}
User user = UserCommand.Create.toEntity(ucrDto, passwordEncoder, ADMIN_KEY);
userRepository.save(user);
String token = jwtUtil.createToken(user.getId(), user.getRole());
// MapStruct 를 통해 Entity-> ResponseDto
UserCreateResponseDto resDto = userMapper.userToUserCreateResponseDto(user);
// ResponseDto 내의 유저 Token 삽입
resDto.setToken(token);
return resDto;
}
public void updateUser(User user, UserUpdateRequestDto uurDto) {
if (userRepository.existsByEmail(uurDto.getEmail())) {
throw new CustomException(ALREADY_EMAIL_USER);
}
if (userRepository.existsByUserName(uurDto.getUserName())) {
throw new CustomException(ALREADY_USERNAME_USER);
}
UserCommand.Update.executeUpdate(user, uurDto);
}
public UserCreateResponseDto createUser(UserCreateRequestDto ucrDto) {
validateUserUniqueness(ucrDto.getEmail(), ucrDto.getUserName());
User user = UserCommand.Create.toEntity(ucrDto, passwordEncoder, ADMIN_KEY);
userRepository.save(user);
String token = jwtUtil.createToken(user.getId(), user.getRole());
// MapStruct 를 통해 Entity-> ResponseDto
UserCreateResponseDto resDto = userMapper.userToUserCreateResponseDto(user);
// ResponseDto 내의 유저 Token 삽입
resDto.setToken(token);
return resDto;
}
public void updateUser(User user, UserUpdateRequestDto uurDto) {
validateUserUniqueness(uurDto.getEmail(), uurDto.getUserName());
UserCommand.Update.executeUpdate(user, uurDto);
}
private void validateUserUniqueness(String email, String userName) {
if (userRepository.existsByEmail(email)) {
throw new CustomException(ALREADY_EMAIL_USER);
}
if (userRepository.existsByUserName(userName)) {
throw new CustomException(ALREADY_USERNAME_USER);
}
}
validateUserUniqueness 를 통해 값을 검증해주는 식으로 코드를 변경.
@Test
void matches_메서드가_정상적으로_동작한다() {
// given
String rawPassword = "testPassword";
String encodedPassword = passwordEncoder.encode(rawPassword);
// when
boolean matches = passwordEncoder.matches(encodedPassword, rawPassword);
// then
assertTrue(matches);
}

@Test
void matches_메서드가_정상적으로_동작한다() {
// given
String rawPassword = "testPassword";
String encodedPassword = passwordEncoder.encode(rawPassword);
// when
boolean matches = passwordEncoder.matches(rawPassword,encodedPassword);
// then
assertTrue(matches);
}
}

matches 메소드의 매개변수의 순서가 틀려 비교가 안되던 문제.
@Test
public void manager_목록_조회_시_Todo가_없다면_NPE_에러를_던진다() {
// given
long todoId = 1L;
given(todoRepository.findById(todoId)).willReturn(Optional.empty());
// when & then
InvalidRequestException exception = assertThrows(InvalidRequestException.class, () -> managerService.getManagers(todoId));
assertEquals("Manager not found", exception.getMessage());
}

@Test
public void manager_목록_조회_시_Todo가_없다면_IRE_에러를_던진다() {
// given
long todoId = 1L;
given(todoRepository.findById(todoId)).willReturn(Optional.empty());
// when & then
InvalidRequestException exception = assertThrows(InvalidRequestException.class, () -> managerService.getManagers(todoId));
assertEquals("Todo not found", exception.getMessage());
}

NullPointException 이 아닌 InvalidRequestException 를 던지기에 메서드명을 IRE 로 수정
@Test
public void comment_등록_중_할일을_찾지_못해_에러가_발생한다() {
// given
long todoId = 1;
CommentSaveRequest request = new CommentSaveRequest("contents");
AuthUser authUser = new AuthUser(1L, "email", UserRole.USER);
given(todoRepository.findById(anyLong())).willReturn(Optional.empty());
// when
ServerException exception = assertThrows(ServerException.class, () -> {
commentService.saveComment(authUser, todoId, request);
});
// then
assertEquals("Todo not found", exception.getMessage());
}

@Test
public void comment_등록_중_할일을_찾지_못해_에러가_발생한다() {
// given
long todoId = 1;
CommentSaveRequest request = new CommentSaveRequest("contents");
AuthUser authUser = new AuthUser(1L, "email", UserRole.USER);
given(todoRepository.findById(anyLong())).willReturn(Optional.empty());
// when
InvalidRequestException exception = assertThrows(InvalidRequestException.class, () -> {
commentService.saveComment(authUser, todoId, request);
});
// then
assertEquals("Todo not found", exception.getMessage());
}

InvalidRequestException 이 아닌 ServerException 을 던져주던 문제.
@Transactional
public ManagerSaveResponse saveManager(AuthUser authUser, long todoId, ManagerSaveRequest managerSaveRequest) {
// 일정을 만든 유저
User user = User.fromAuthUser(authUser);
Todo todo = todoRepository.findById(todoId)
.orElseThrow(() -> new InvalidRequestException("Todo not found"));
if (!ObjectUtils.nullSafeEquals(user.getId(), todo.getUser().getId())) {
throw new InvalidRequestException("담당자를 등록하려고 하는 유저가 일정을 만든 유저가 유효하지 않습니다.");
}
User managerUser = userRepository.findById(managerSaveRequest.getManagerUserId())
.orElseThrow(() -> new InvalidRequestException("등록하려고 하는 담당자 유저가 존재하지 않습니다."));
if (ObjectUtils.nullSafeEquals(user.getId(), managerUser.getId())) {
throw new InvalidRequestException("일정 작성자는 본인을 담당자로 등록할 수 없습니다.");
}
Manager newManagerUser = new Manager(managerUser, todo);
Manager savedManagerUser = managerRepository.save(newManagerUser);
return new ManagerSaveResponse(
savedManagerUser.getId(),
new UserResponse(managerUser.getId(), managerUser.getEmail())
);
}

@Transactional
public ManagerSaveResponse saveManager(AuthUser authUser, long todoId, ManagerSaveRequest managerSaveRequest) {
// 일정을 만든 유저
User user = User.fromAuthUser(authUser);
Todo todo = todoRepository.findById(todoId)
.orElseThrow(() -> new InvalidRequestException("Todo not found"));
if (!ObjectUtils.nullSafeEquals(
user.getId(),
Optional.ofNullable(todo.getUser())
.map(User::getId)
.orElseThrow(
() -> new InvalidRequestException("담당자를 등록하려고 하는 유저가 일정을 만든 유저가 유효하지 않습니다.")))) {
throw new InvalidRequestException("담당자를 등록하려고 하는 유저가 일정을 만든 유저가 유효하지 않습니다.");
}
User managerUser = userRepository.findById(managerSaveRequest.getManagerUserId())
.orElseThrow(() -> new InvalidRequestException("등록하려고 하는 담당자 유저가 존재하지 않습니다."));
if (ObjectUtils.nullSafeEquals(user.getId(), managerUser.getId())) {
throw new InvalidRequestException("일정 작성자는 본인을 담당자로 등록할 수 없습니다.");
}
Manager newManagerUser = new Manager(managerUser, todo);
Manager savedManagerUser = managerRepository.save(newManagerUser);
return new ManagerSaveResponse(
savedManagerUser.getId(),
new UserResponse(managerUser.getId(), managerUser.getEmail())
);
}

NPE 가 발생하면서 프로그램이 종료되던 문제.Optional 을 통해서 Todo 가 가지는 User의 값이 NULL 일 경우 테스트코드의 예외가 발생하도록 수정.@Slf4j(topic = "ManagerLogAop")
@Aspect
@Component
@RequiredArgsConstructor
public class ManagerLogAop {
@Pointcut("execution(* org.example.expert.domain.comment.controller.CommentAdminController.*(..))")
private void deleteComment() {}
@Pointcut("execution(* org.example.expert.domain.user.controller.UserAdminController.*(..))")
private void changeUserRole() {}
@Around("deleteComment() || changeUserRole()")
public Object execute(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// 요청시각 측정
LocalDateTime startTime = LocalDateTime.now();
// 요청 정보를 가져옴
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
Object output = proceedingJoinPoint.proceed();
// 요청 본문 캡쳐
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
String requestBody = new String(requestWrapper.getContentAsByteArray(), StandardCharsets.UTF_8);
if (requestBody.isEmpty()) {
requestBody = proceedingJoinPoint.getArgs()[0].toString();
}
// 접근 정보 가져오기
Long userId = (Long) requestWrapper.getAttribute("userId");
String url = requestWrapper.getRequestURI();
// 응답 본문 캡쳐
ContentCachingResponseWrapper responseWrapper =
new ContentCachingResponseWrapper(attributes.getResponse());
try {
responseWrapper.copyBodyToResponse();
return output;
} finally {
int status = responseWrapper.getStatus();
log.info(
"UserID : {} | URL : {} | RequestTime : {} | RequestBody OR PathVariable : {} | StatusCode : {}",
userId,
url,
startTime,
requestBody,
status);
}
}
}

Pointcut 을 이용하여 ADMIN 만 사용가능한 2개의 메소드를 지정. ADMIN 전용 패키지를 두거나 하는식으로 하나로 관리가 가능할 것 같다.
요청정보, 응답정보 둘 다 필요하기에 @Around("deleteComment() || changeUserRole()") 를 사용하여 전 후 로 실행되도록 설정하였다.
ContentCachingRequestWrapper 를 사용하였는데 그 이유는
HttpServletRequest은 한 번 읽으면 더이상 접근이 불가능함.HttpServletRequest 를 복사해둔 데이터로써 저장한다.ContentCachingResponseWrapper 또한 비슷한 이유인데 다른점은 HttpServletResponse 는 한 번 작성되면 수정이 불가능하다.
요구사항은 Request/Response 까지 요구하였지만 두 메소드 다 반환값이 없기에 HTTPStatusCode 로 사양을 변경하였다.
RequestBody 가 존재하지 않을경우 @PathVariable 값을 리턴하도록 했다.
참조 : https://velog.io/@dlwlrma/%EC%95%8C%EA%B3%A0-%EC%93%B0%EC%9E%90-ContentCachingRequestWrapper

private void validateUserUniqueness(String email, String userName) {
if (userRepository.existsByEmail(email)) {
throw new CustomException(ALREADY_EMAIL_USER);
}
if (userRepository.existsByUserName(userName)) {
throw new CustomException(ALREADY_USERNAME_USER);
}
}
2-1 의사결정 과정 :
프로젝트 규모가 커질수록 검증조건이 많아질거라 판단 성능을 위해 2번 방법을 선택.
2-2 해결 과정 :
private void validateUserUniqueness(String email, String userName) {
Optional<User> foundUser = userRepository.findByEmailOrUserName(email, userName);
if (foundUser.isPresent()) {
User existingUser = foundUser.get();
existingUser.validateUniqueEmail(email);
existingUser.validateUniqueUserName(userName);
}
}
findByEmailOrUserName JPA 쿼리 메소드를 통해 하나의 쿼리로 N개 요소의 중복조회가 가능하도록 변경.User Entity 에게 중복의 대한 부분을 검증하도록 책임을 위임하였음.3-1 회고 : 코드의 최적화 뿐만 아니라 DB의 쿼리 최적화 또한 중요한 요소라고 느낌 오히려 성능면 최적화에선 오히려 코드최적화 보다 중요할지도 모를거란 생각이 들었다.
3-2 전후데이터 비교
개선전 :

개선 후:

1개의 쿼리만이 발생하는 것을 확인할 수 있다.

1. 문제인식 : NewsFeed 프로그램에서 좋아요 시스템을 개발 중 댓글과 게시글에 대한 좋아요를 하나의 테이블로 관리했을때 다른쪽 컬럼에는 NULL 이 삽입되는 상황발생.
2 - 1 의사결정 과정 :
-1 처리가짜객체 가 필요.DB 데이터의 대한 무결성과 가짜 객체 가 생성됨에 따른 필터링작업등을 고려시 2번이 적합한 상태라 판단.
2 - 2 해결방안

public class CommentLike {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", nullable = false)
private Member member;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "comment_id", nullable = false)
private Comment comment;
public void addLikeComment(Member member, Comment comment) {
this.member = member;
this.comment = comment;
}
}
================================================================================================
public class NewsLike {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", nullable = false)
private Member member;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "news_id", nullable = false)
private News news;
public void addLikeNews(Member member, News news) {
this.member = member;
this.news = news;
}
}
해결 완료
3-1 회고 :
3-2 전후데이터 비교

