끝을 보는 용기

Day 086 - 심화 Spring 2주차 완강, Spring 심화 프로젝트 1단계 완료, 팀장 특강을 열어도 될까?

writingforever162 2024. 12. 31. 23:49

1. 프로젝트 진행 상황 및 계획

🥇 Spring 심화 프로젝트 필수 과제 2단계 끝내기 (진행 전, 25.01.01 완료 목표)
🥈 심화 Spring 3주 차 완강하기 (진행 중, 25.01.01 완료 목표)

🥉 Spring 심화 프로젝트 도전 과제 일정 계획하기 (진행 전, 25.01.02 완료 목표)
4️⃣ Spring 심화 프로젝트 필수 과제 1단계 끝내기 (완료)
5️⃣ 심화 Spring 2주 차 완강하기 (완료)

 

2. Spring 심화 프로젝트 필수 과제 1단계: 일정 관리 앱 Develop 보완하기

(1) [링크] 보완 전 코드

(2) [링크] 보완 후 코드

(3) 보완한 부분 

① 세터(Setter) 대신 메서드(method) 사용하기

② 사용하지 않는 생성자는 public 대신 protected로 바꾸기 

⑤ varchar 대신 length 사용하기 

더보기
package com.example.plan.comment7.service;

import com.example.plan.comment7.dto.response.CommentResponseDto;
import com.example.plan.comment7.entity.Comment;
import com.example.plan.comment7.repository.CommentRepository;
import com.example.plan.plan7.entity.Plan;
import com.example.plan.plan7.repository.PlanRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@RequiredArgsConstructor
@Service
public class CommentServiceImpl implements CommentService {
    private final PlanRepository planRepository;
    private final CommentRepository commentRepository;

    @Transactional
    @Override
    public CommentResponseDto save(String content, Long planId) {
        Plan foundplan = planRepository.findByIdOrElseThrow(planId);

        Comment commentToSave = new Comment(content);

        commentToSave.setPlan(foundplan);
        commentToSave.setMember(foundplan.getMember());

        Comment savedComment = commentRepository.save(commentToSave);

        return CommentResponseDto.toDto(savedComment);
    }
}
더보기
package com.example.plan.comment7.service;

import com.example.plan.comment7.dto.response.CommentResponseDto;
import com.example.plan.comment7.entity.Comments;
import com.example.plan.comment7.repository.CommentRepository;
import com.example.plan.plan7.entity.Plan;
import com.example.plan.plan7.repository.PlanRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;

@RequiredArgsConstructor
@Service
public class CommentServiceImpl implements CommentService {
    private final PlanRepository planRepository;
    private final CommentRepository commentRepository;

    @Transactional
    @Override
    public CommentResponseDto createComment(
            String content,
            Long planId
    ) {
        Plan foundPlan = planRepository
                .findByIdAndIsDeletedFalse(planId)
                .orElseThrow(
                        () -> new ResponseStatusException(
                                HttpStatus.NOT_FOUND,
                                "Id does not exist"
                        )
                );

        Comments commentToSave = new Comments(content);

        commentToSave.updatePlan(foundPlan);

        commentToSave.updateMember(foundPlan.getMember());

        Comments savedComment = commentRepository.save(commentToSave);

        return CommentResponseDto.toDto(savedComment);
    }
}
더보기
package com.example.plan.comment7.entity;

import com.example.plan.base.BaseEntity;
import com.example.plan.member7.entity.Member;
import com.example.plan.plan7.entity.Plan;
import jakarta.persistence.*;
import lombok.Getter;
import org.hibernate.annotations.Comment;

/*
[수정 전] Comment
[수정 후] Comments
[수정 이유] Comment 어노테이션(annotation, @Comment)과 구분
 */
@Getter
@Entity
@Table(name = "comments7")
public class Comments extends BaseEntity {
    @Comment("댓글 식별자")
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(columnDefinition = "BIGINT")
    private Long id;

    @Comment("댓글 내용")
    @Column(
            name = "content",
            length = 255
    )
    private String content;

    @ManyToOne
    @JoinColumn(
            name = "plan_id",
            nullable = false
    )
    private Plan plan;

    @ManyToOne
    @JoinColumn(
            name = "member_id",
            nullable = false
    )
    private Member member;

    protected Comments() {
    }

    public Comments(String content) {
        this.content = content;
    }

    public void updatePlan(Plan plan) {
        this.plan = plan;
    }

    public void updateMember(Member member) {
        this.member = member;
    }

    public void updateContent(String content) {
        this.content = content;
    }
}

③ JPQL을 전부 쿼리 메서드(query method)로 고치기 

④ 예외 처리는 전부 서비스 레이어(Service Layer)로 옮기기

더보기
package com.example.plan.plan7.repository;

import com.example.plan.plan7.entity.Plan;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.http.HttpStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;

import java.util.Optional;

public interface PlanRepository extends JpaRepository<Plan, Long> {
    @Query("SELECT p FROM Plan p WHERE p.isDeleted IS NULL")
    Page<Plan> findAllExceptDeleted(Pageable pageable);

    @Query("SELECT p FROM Plan p WHERE p.id = :id AND p.isDeleted IS NULL")
    Optional<Plan> findByIdExceptDeleted(Long id);

    default Plan findByIdOrElseThrow(Long id) {
        return findByIdExceptDeleted(id).orElseThrow(
                () -> new ResponseStatusException(
                        HttpStatus.NOT_FOUND
                        , "Id does not exist"
                )
        );
    }

    @Transactional
    @Modifying
    @Query(
            "UPDATE Plan p " +
                    "SET p.isDeleted = true, p.deletedAt = CURRENT_TIMESTAMP " +
                    "WHERE p.id = :id " +
                    "AND p.isDeleted IS NULL"
    )
    int softDeleteById(Long id);

    @Transactional
    @Modifying
    @Query(
            "UPDATE Plan p " +
                    "SET p.isDeleted = TRUE, p.deletedAt = CURRENT_TIMESTAMP " +
                    "WHERE p.member.id = :id " +
                    "AND p.isDeleted IS NULL"
    )
    void softDeleteByMemberId(Long id);
}
더보기
package com.example.plan.plan7.repository;

import com.example.plan.plan7.entity.Plan;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;
import java.util.Optional;

public interface PlanRepository extends JpaRepository<Plan, Long> {

    Page<Plan> findAllByIsDeletedFalse(Pageable pageable);

    Optional<Plan> findByIdAndIsDeletedFalse(Long planId);

    List<Plan> findAllByMemberIdAndIsDeletedFalse(Long memberId);
}

⑥ DTO를 모두 레코드 클래스(record class)로 바꾸기 

⑦ 의미가 더 잘 드러나도록 변수 및 메서드(method) 이름 다듬기 

더보기
package com.example.plan.plan7.dto.request;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import org.hibernate.validator.constraints.Length;

@Getter
public class CreatePlanRequestDto {
    @NotBlank(message = "일정의 제목 입력은 필수입니다.")
    @Length (min = 1, max = 20)
    private final String title;

    @NotEmpty(message = "null과 빈값을 허용하지 않습니다.")
    @Length(max = 200)
    private final String task;

    @NotNull(message = "사용자 id 입력은 필수입니다.")
    private final Long userId;

    public CreatePlanRequestDto(
            String title
            , String task
            , Long userId

    ) {
        this.title = title;
        this.task = task;
        this.userId = userId;
    }
}
더보기
package com.example.plan.plan7.dto.request;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.hibernate.validator.constraints.Length;

public record CreatePlanRequestDto(
        @NotBlank(message = "일정의 제목 입력은 필수입니다.")
        @Length(min = 1, max = 20)
        String title,

        @NotEmpty(message = "null과 빈값을 허용하지 않습니다.")
        @Length(max = 200)
        String task,

        @NotNull(message = "사용자 id 입력은 필수입니다.")
        Long memberId
) {
}

(4) [링크] 리팩토링(refactoring)을 진행할 때 참고한 글 

 

3. 팀장 특강을 열어도 될까?

오늘 뉴스피드 프로젝트를 같이 진행한 팀원이 이번 프로젝트 때 팀장 해볼 용기를 내고 싶은데 잘할 수 있을지 고민된다고 얘기했다. 이런저런 이야기를 나누면서 만약 자신이 정말 팀장이 된다면 종종 질문하겠다고 할 때 문득 '팀장 특강을 열면 괜찮으려나?' 싶은 생각이 머릿속을 스쳤다. 특강이란 거창한 표현을 썼지만 막상 열었을 때 한두 명만 오고 말 수도 있었다. 그건 별로 중요하지 않았다. '몇 명이 들으러 왔는가?'보다는 '한 사람이라도 이 특강을 원했다'는 사실이 훨씬 더 중요했다. 

 

'그냥 질문받을 때마다 대답해 주면 그만 아닌가?'

 

물론 질문할 때마다 메시지로 대답해도 상관은 없겠지만, 팀원이 용기를 낸 만큼 공들여 대답해 주고 싶었다. 서로 이야기를 나누면서 나 또한 무언가를 배울 수도 있었다. 팀원이 용기를 낸 만큼, 정성을 가득 담아 대답해 주고 싶었다.

 

때마침 팀장 관련 책도 이번 주에 다 읽을 예정인지라, 다음 주 월요일 오후나 저녁에 일정을 잡으면 좋지 않을까 싶다. '팀장'이라고는 말하나 사실은 조장이고, 그저 팀 프로젝트 때마다 팀장을 했을 뿐인데. 그 횟수도 딱 두 번이고. 

 

팀원이 진지하게 물어봤으니, 팀장도 그만큼 진지하게 대답해 줘야 한다.

 

우선은 이번 주에 과제 마치는 속도를 전보다 더 높여 봐야겠다.