2021-03-12-TIL

[PR]

미션 2: 데이터베이스 활용

안녕하세요, 코드스쿼드 백엔드 반의 August입니다. 항상 상세하고 좋은 리뷰 너무 감사합니다! 이번 과제는 주어진 요구사항인 H2 DB 설정 및 적용, 회원가입 및 회원목록 기능 구현, 개인정보 수정 기능(미션1에서 진행한 것을 약간 수정) 했습니다.

  • 서버 주소 : https://august-qna.herokuapp.com/

구현 내용

미션1로부터 변경된 부분만 언급하겠습니다!

  • QuestionController : 기존에 List의 size()로 id를 관리하던 부분은 모두 없어졌습니다. (@GeneratedValue)
  • UserController : 기존에 user가 null인지 체크하는 부분은 service 계층으로 넘겨주었습니다. update()에서는 패스워드가 일치하는지 확인하고 일치한다면 수정하도록 했습니다.
  • Question : id를 자동으로 증가하도록 했고, 나머지 칼럼들은 각 역할에 맞게 일부는 not null 제한을 두었습니다.
  • User : 마찬가지로 id는 자동증가하고, 나머지 칼럼의 속성도 애노테이션으로 지정했습니다. matchPassword를 추가하여 패스워스 일치여부를 확인합니다.
  • QuestionRepository/UserRepository : JPA를 적용하는 방식으로 변경했습니다.
  • QuestionService : 대부분 QuestionRepository에서 제공하는 JPA 메서드를 그대로 전송해주는 역할만 하지만 findQuestionById에서는 Optional을 검증하는 로직을 포함하여 컨트롤러에서는 코드상에 변경사항이 없도록 했습니다.
  • UserService : 마찬가지로 findUserById만 Optional 검증 로직이 포함되어 있습니다.

[CODE REVIEW]

미션2: 데이터베이스 적용 by 새리

[August]

  • helper 기능이 궁금합니다.
  • Question에 strategy = GenerationType 이부분 궁금합니다. -> 안해서 오류 나는 경우가 많다.
  • public 생성자를 두는게 나은가요? 아니면 private이 나은가요? 저는 에러가 발생하던데..
  • Optional로 받지 않는 방법도 있습니다.

[아이작]

  • 레퍼지토리 패키지 분리
  • Logger사용으로 전환

[Jane]

  • 날짜가 String인데 LocalDateTime으로 변경하는 것이 좋아보인다.
  • password를 getter로 가져올 필요가 없어보인다.
  • 메서드명도 matchPassword에서 hasMatchingPassword로
  • 255넘어가면 에러나는데 column length를 늘리는게 나을 것 같다.

[bat]

  • UserController에서 matchPassword를 User에서 모두 처리하는 것으로 변경하는 것이 좋을 것 같다.

[kyu]

  • 디폴트 생성자를 정의 안 해줘도 되던데? -> 기본적으로 생성자가 아예없다면 컴파일러가 디폴트 생성자를 삽입한다. 하지만 다른 생성자가 있다면 오버로딩된 디폴트 생성자를 반드시 정의해주어야 한다.

미션2: 데이터베이스 적용 by August

[Kyu]

  • @Column(nullable = false)는 테스트 해보았더니 입력 폼에 내용이 없어도 써지던데, null이 오는 경우는 어떤 경우?
    • [아이작] : 만약 password 입력폼이 아예 없다면 User객체로 넘겨줄 때 password가 null로 넘어갑니다.
    • 실제로 입력 폼에 내용이 없는 상태로 submit하면 null이 아니라 빈 문자열로 채워진 객체가 컨트롤러로 넘겨진다. 따라서 빈 문자열에 대한 처리를 해주려면 별도의 방법이 필요하다.
    • [bat] : String::trim().isEmpty()로 빈 문자열인지 검증(validation)이 가능하다.
    • [Jane] : @NotEmpty 애노테이션으로 빈 문자열 체크가 가능하다. 단, build.gradle에 의존을 추가해주어야한다! implementation 'org.springframework.boot:spring-boot-starter-validation'
  • 왜 QuestionRepositoy는 CrudRepository를 상속하고, UserRepository는 JpaRepository를 상속하는지? => 특별한 이유는 없다.
  • User나 Question에서 setter만 사용하는데, 생성자를 통해서 가져오는 것과 다른지?
    • 한 번만 생성하고 바꿀 필요가 없는 데이터는 생성자만을 통해서 생성하는게 맞는것 같고, 수정할 가능성이 있는 필드에 대해서는 setter를 두는 것이 맞는 것 같다. // TODO: remove to make it unmodifiable

[bat]

  • private final UserService userService; 로 선언할 때 final 키워드 추가. 어차피 하나만 주입받는 싱글톤이므로 + 변경 방지
  • TODO: Column(length = 2000 ~ 4000) 기본값이 255

[아이작]

logging.level.com=DEBUG

개발할 때만 디버그 레벨을 쓰고, 상용으로 배포할 때는 이 설정값만 레벨업하고 배포하면 되니까 이러한 방법을 권장한 듯.

[Jane]

  • User::update는 setter를 사용하지 않는 것이 낫다. 내부자원에 대한 접근이 가능한데, 굳이 외부에서 사용하기 위한 메서드인 setter(내부 메서드)를 통해서 접근할 필요가 없다.

  • UserController::create 에서 사용자 계정을 생성과정에서 서비스 계층에서 검증을 하면 좋겠다.

    TODO: validation logic in service layer -> exist userID or sth…

  • UserController::update에서 user.update(newUser)를 했으면 userService.save(user)로 굳이 다시 저장해줄 필요가 없다고 한다. 이미 생성된 계정에 대해서는 알아서 레코드를 찾아서 변경해준다. by JPA?

@GeneratedValue(strategy = GenerationType.IDENTITY)

  • https://stackoverflow.com/questions/20603638/what-is-the-use-of-annotations-id-and-generatedvaluestrategy-generationtype

@NotEmpty, @NotNull, @NotBlank

  • https://www.baeldung.com/java-bean-validation-not-null-empty-blank
  • https://sanghye.tistory.com/36

그런데 애노테이션을 필드에 붙여주면 별도의 해당 경우에 500에러가 발생하고 추가적인 에러 핸들링을 해주어야 함.

  • https://medium.com/sprang/validation-and-exception-handling-with-spring-ba44b3ee0723