Develope Story

2019-09-07 [Spring-study] ATDD 1단계

|

ATDD 1단계

  • 요구사항
    • loginAcceptanceTest 구현
    • userAcceptanceTest 통과하기위한 구현
    • loginAcceptanceTest 통과하기위한 구현
  • 공부한 내용

      public HtmlFormDataBuilder addParameter(String key, Object value) {
            this.params.add(key, value);
            return this;
        }
    
    • this 호출후 메소드 적용 → this 리턴

      .addParameter(“_method”, “put”) .addParameter(“password”, “test”) .addParameter(“name”, “자바지기2”) .addParameter(“email”, “javajigi@slipp.net”)

    메소드를 이어 붙이게 해줌

    ### ATDD

    기존의 TDD의 기능을 기능 테스트 수준까지 확장한 것

    https://javacan.tistory.com/entry/TDD-ATDD

    기존 TDD

    • test 작성
    • 실패
    • 구현
    • 리팩토링
    • 외부와의 의존관계가 없기 때문에 한계

    ⇒ 기능 통합시 문제점 발생 ⇒ ATDD 등장

    • 전구간에 대한 테스트 : 시스템이 동작하는 지 테스트
      1. 인수테스트 작성
      2. 실패
      3. 단위테스트
      4. 실패
      5. 단위 구현
      6. 통과 (반복)
      7. 인수 통과..(반복)
      8. 리팩토링

    ### Layered architecture 에서 테스트

    • 시간이 충분하다면 모든 부분을 테스트하는 것이 좋다. 하지만 현실은 그렇지 않다. 어느 부분에 우선순위를 두고 테스트하는 것이 바람직할까?

      controller → service → repository

    ### Basic Auth

    사용이유

    • 로그인 테스트를 위한 테스트는 2번씩 이루어짐
      1. Session ID를 발급받기 위한 테스트를 위한 요청
      2. 테스트할 기능에 대한 요청
    • session을 꺼내서 → 기능 테스트

    이런 단점을 보완하기 위해서 BasicAuth를 활용

    클라이턴트: 아이디 + 비밀번호 ⇒ 인코딩 ⇒ 서버전송

    서버 : Basic 데이터 확인 → 로그인

    • Controller 실행전에 로그인 처리

    HandlerMethodArgumentResolver를 활용한 로그인 사용자 정보 추출

    • @LoginUser 어노테이션 사용해서 로그인 되어있는지 확인

    • 설정 - nextstep참고

        public class BasicAuthInterceptor extends HandlerInterceptorAdapter {
              
          private static final Logger log = LoggerFactory.getLogger(BasicAuthInterceptor.class);
              
          @Autowired
          private UserService userService;
              
          public BasicAuthInterceptor() { }
              
          public BasicAuthInterceptor(UserService userService) {
            this.userService = userService;
          }
              
          @Override
          public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
              throws Exception {
        	 String authorization = request.getHeader("Authorization");
            log.debug("Authorization : {}", authorization);
            if (authorization == null || !authorization.startsWith("Basic")) {
              return true;
            }
              
        //디코더 하는 부분
            String base64Credentials = authorization.substring("Basic".length()).trim();
            String credentials = new String(Base64.getDecoder().decode(base64Credentials),
                Charset.forName("UTF-8"));
            final String[] values = credentials.split(":", 2);
            log.debug("username : {}", values[0]);
            log.debug("password : {}", values[1]);
             try {
          User user = userService.login(values[0], values[1]);
          log.debug("Login Success : {}", user);
          request.getSession().setAttribute(HttpSessionUtils.USER_SESSION_KEY, user);
          return true;
        } catch (UnAuthenticationException e) {
          return true;
             }
          }
        }
      

    ### Mock

    • @mock - 목객체생성
    • db같은 외부 api를 테스트 할 때 사용

    참고 : https://jdm.kr/blog/222

    ### RestRespone

    RestStatus : 상태 객체

      public class RestStatus {
          private boolean status;
        
          public RestStatus(boolean status) {
              this.status = status;
          }
        
          public boolean isStatus() {
              return status;
          }
        
          @Override
          public String toString() {
              return "RestResponse [status=" + status + "]";
          }
      }
    

    RestResponse 객체 : 상태객체 상속하고 전달할 객체

      public class RestResponse extends RestStatus {
          private Map<String, Object> result;
        
          public RestResponse() {
              super(true);
              result = new HashMap<>();
          }
        
          public void addAttribute(String key, Object value) {
              result.put(key, value);
          }
        
          public Map<String, Object> getResult() {
              return result;
          }
      }
    

    ### @Lob

    JPA 어노테이션 참고 : https://m.blog.naver.com/dimigozzang/220510288775

      public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
              NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
              User user = HttpSessionUtils.getUserFromSession(webRequest);
              if (!user.isGuestUser()) {
                  return user;
              }
        
              LoginUser loginUser = parameter.getParameterAnnotation(LoginUser.class);
              if (loginUser.required()) {
                  throw new UnAuthorizedException("You're required Login!");
              }
              return user;
          }
        
      public static final GuestUser GUEST_USER = new GuestUser();
    
    • 세션 코드

        public static User getUserFromSession(NativeWebRequest webRequest) {
                if (!isLoginUser(webRequest)) {
                    return User.GUEST_USER;
                }
                return (User) webRequest.getAttribute(USER_SESSION_KEY, WebRequest.SCOPE_SESSION);
            }
      
    • 세션 → 로그인 체크 → 로그인 되어있지 않을 경우 null이 아닌 guestUser 객체 반환

    @Transaction

    ### 트랜잭션

    • 원자성 (Atomicity)
    • 일관성 (Consistency)
    • 독립성 (Isolation)
    • 지속성 (Durability)

    수정할때 데이터를 꺼내면 수정한 데이터와 꺼내온 데이터가 다름 → 이런것들을 방지하기위함

    • 자세한 내용 구글링 하면 많이 나옴

        @OneToMany(mappedBy="question")
        @Where(clause = "deleted = false")
        @OrderBy("id ASC")
        private List<Answer> answers;
      
        where
            (
                answers0_.deleted = 0
            )
            and answers0_.question_id=?
        order by
            answers0_.id asc
      

    asc 오름차순

    where

      where
              (
                  answers0_.deleted = 0
              )
    

    Interface 활용

      @Override
        public String generateUrl() {
          return String.format("/questions/%d", getId());
        }
    
  • 리뷰
    • tab, space 조정 → diff 최소화
      • 들여쓰기 2칸 → 4칸 조정
    • 객체가 비어있을 땐 EntityNotFoundException사용
    • equals 재정의
    • resource 사용자제

2019-08-29 [Spring-study] QnA 4단계 - 객체 관계 매핑

|

QnA 4단계 - 객체 관계 매핑

요구사항

  • 답변 추가하기
  • 답변 수정, 삭제기능

개인 요구사항

  • 비밀번호 암호화

공부한 내용

Cascade Type 종류

  • CascadeType.PERSIST 엔티티를 영속화 할 때이 필드에 보유 된 엔티티도 유지합니다. EntityManager가 flush 중에 새로운 엔티티를 참조하는 필드를 찾고이 필드가 CascadeType.PERSIST를 사용하지 않으면 오류이므로이 Cascade 규칙의 자유로운 적용을 제안합니다.
  • CascadeType.MERGE 엔티티 상태를 병합 할 때, 이 필드에 보유 된 엔티티도 병합하십시오.
  • CascadeType.REFRESH 엔티티를 새로 고칠 때, 이 필드에 보유 된 엔티티도 새로 고칩니다.
  • CascadeType.REMOVE 엔티티를 삭제할 때, 이 필드에 보유 된 엔티티도 삭제하십시오.
  • CascadeType.DETACH 부모 엔티티가 detach()를 수행하게 되면, 연관된 엔티티도 detach() 상태가 되어 변경사항이 반영되지 않는다.
  • CascadeType.ALL 모든 Cascade 적용

https://postitforhooney.tistory.com/entry/JavaJPAHibernate-CascadeType%EB%9E%80-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EC%A2%85%EB%A5%98

FetchType

  • EAGER - 즉시 로딩 : 엔티티 조회 시 연관된 엔티티도 함께 조회
    • 연관된 엔티티 같이 업데이트
  • LAZY - 지연 로딩 : 엔티티를 실제 사용 할때 조회.
    • ex) 댓글 더보기

SLF4J 로그 사용

slf4j 추상 -> logger

System.out을 사용 하지 않는 이유

  • 리소스를 너무 많이 잡아 먹음
  • 계층적인 로그(우선순위) 처리
    • trace, debug, info, warn, error 순 -> 높
  • 여러가지 장점 링크 참조

기능 (Facade pattern)

  • API
  • Binding
    • SLF4J 인터페이스를 로깅 구현체와 연결하는 어댑터 역할
  • Bridging
    • 다른 로깅 API로의 Logger 호출을 SLF4J 인터페이스로 연결(redirect)하여 SLF4J API가 대신 처리하는 어댑터 역할

참고 https://gmlwjd9405.github.io/2019/01/04/logging-with-slf4j.html

Facade pattern(최광훈교수님꺼 퍼옴)

  • 서브 시스템의 세부 클래스들을 직접 사용하지 못하도록 막고 이 클래스들을 대표하는 클래스들을 통해서 간적접으로 사용하게 하는 방법

​ 예제) 컴파일러 서브 시스템, 가상 메모리 프레임워크

  • 장점 :

    1) 사용자와 서브 시스템 간의 결합도를 줄인다.

    2) 서브 시스템 내부의 특정 클래스들만 공개하고 나머지는 숨길 수 있다.

###BCrypt 비밀번호 암호화 => bcryptpasswordencoder로 변경함

BCrypt.hashpw(password, BCrypt.gensalt(10))

비밀번호를 해싱하는 메소드

  • 비밀번호가 길어지면 해싱하는 시간 길어져서 제한하기 위해 BCrypt.gensalt(10) 사용
BCrypt.checkpw(password, hashedPassword)

암호화된 비밀번호와 비교

궁금증

=> 스크린샷 2019-09-04 오후 5.08.47

크롬 개발자 도구에 password가 나타나는 문제점을 해결하지 못함

참고 https://stackoverflow.com/questions/3353930/password-sent-via-post-secure

=> https를 써야함 + I use javascript to hash the password before submitting the form.

  • 프론트로 js로 암호화 한번 걸침

리뷰

혁진이형

  • JpaRepository와 CrudRepository 의 차이점에 대해서 말씀해주세요

  • JpaRepository 는 PagingAndSortingRepository 와 CrudRepository 확장
  • PagingAndSortingRepository 와 CrudRepository 기능
  • CrudRepository = CRUD 기능 제공
  • PagingAndSortingRepository = 페이지 매김 및 정렬 레코드를 수행하는 메소드를 제공

  • JpaRepository = 일괄 처리에서 지속성 컨텍스트를 삭제하고 레코드를 삭제하는 것과 같은 JPA 관련 메소드를 제공

위에서 언급 한 상속 때문에 JpaRepository는 CrudRepository 및 PagingAndSortingRepository의 모든 기능을 갖습니다. 따라서 JpaRepository와 PagingAndSortingRepository가 제공하는 기능을 저장소에 필요로하지 않는다면 CrudRepository를 사용

https://codeday.me/ko/qa/20190306/7467.html

  • Date 클래스의 사용을 지양해야하는 이유와 대안책을 말해주세요

기본 날짜 클래스 문제점 ( date, calendar ) https://d2.naver.com/helloworld/645609

  • 불변 객체 x = 변한다

    • Set 을통해서 데이터를 수정 할 수 있음.

      • 해결책
      private final Date startTime;
      private final Date endTime;
          
      public Constructor(Date startTime, Date endTime){
        this.startTime = startTime;
        //방어 복사 기법 활용
        this.endTime = new Date(endTime.getTime());
      }
          
      public Date getStarTime(){
        return this.startTime;
      }
          
      //방어 복사 기법 활용
      public Date getEndTime(){
        return new Date(this.endTime.getTime());
      }
      
      • 생성자와 접근자 모두 변경 불가능 클래스

        https://ktko.tistory.com/entry/Effective-Java-39-필요하다면-방어적-복사본을-만들라

  • 헷갈리는 월 지정

    calender.set(1582, Calender.OCTOBER, 4) = calender.set(1582, 10 - 1, 4);
    
  • 일관성 없는 요일 상수

    Calendar.get(Calendar.DAY_OF_WEEK) 일요일 1, 수요일 4

    Date.getDay() 일요일 0, 수요일 3

    • Calender와 Date.getDay()의 요일 상수가 다름
  • Date와 Calendar 객체의 역할 분담

  • 오류에 둔감한 시간대 ID 지정

    @Test
    public void shouldSetGmtWhenWrongTimeZoneId(){  
        TimeZone zone = TimeZone.getTimeZone("Seoul/Asia");
        assertThat(zone.getID()).isEqualTo("GMT");
    }
    

    ‘Asia/Seoul’대신 ‘Seoul/Asia’로 잘못 지정한 코드 - 테스트 통과됨. ㅠ

  • 기타 java.util.Date 하위 클래스의 문제

    • 먼솔인지..
  • 시간 API

결론 : Joda-Time 의 LocalDate, LocalTime, LocalDateTime 많이 씀(https://jeong-pro.tistory.com/163)

  • 생성자 시점에 Date를 초기화 해주면 어떤 문제가 있을까요?

  • 나도 테스트를 해본게 아니라서 틀릴 수도 있는데,Answer에 Entity 어노테이션이 붙어있잖아? JPA는 DB에 이 클래스 정보를 바탕으로 테이블을 만들거나, 이미 있으면 매핑을 시켜그리고 DB 안에 있는 하나의 row 데이터를 findby 로 가져온다고 했을 때,해당 row 를 매핑한 새로운 객체(answer) 가 반환이 되겠지?(그 객체 안에 있는 date는 뭐 아마 처음에 값을 save 했을 때 객체를 만들었을 테니까 그때 생성자를 통해 date가 초기화 되었을거고.)그렇게 초기화 되어서 DB에 저장되어있던 데이터를 다시 findby로 가져와서 새로운 answer 객체에 매핑을 하잖아?그러면 새로운 answer 객체가 생성되면서 기본 생성자가 date를 초기화 시키겠지?그럼 처음에 db에 있었던 date 값과 그걸 매핑 시키는 과정에서 date가 달라지겠지?라는 생각으로 리뷰를 달았었는데,답변 달면서 생각해보니까 생성자를 호출하고 나서 JPA가 DB에서 가져온 값들을 (reflection을 통해) 객체의 필드에 꽃아 넣을 것 같다는 생각이 들어서 결국 date 값 db에서 가져옴 -> 객체 초기화 과정에서 new date 해서 날짜가 달라짐 -> JPA가 가져온 db date 데이터가 필드에 꽃아넣어짐 -> 결국 new date 해서 나온 값 무시될 것 같다는 생각이 들긴한다.결론은 정말 궁금하면 정말 그런지 테스트 해보시고 저도 알려주세요 내가 아는 좋은 방법은 timestamp? 같은 어노테이션을 쓰는 것? (jpa date 같은걸로 검색해서 찾아보면 많이 나올거야)

Entity Listener

@PrePersist, @PreUpdate

prepersist , preupdate 사용법

https://vladmihalcea.com/prepersist-preupdate-embeddable-jpa-hibernate/

-> @CreationTimestamp, @UpdateTimestamp

  • 자동으로 시간 설정해줌…

  • cascade type과 fetchtype 에 대해 설명해주실 수 있나요?

  • getQuestionById 메서드에서 바로 get 을 하시는데, get을 사용하면 어떤 문제가 발생할까요?

    https://tuhrig.de/find-vs-get/

**find **

public interface CrudRepository<T,ID> {
    ...
    Optional<T> findById(ID id);
    Iterable<T> findAll();
    ...
}

Find = 리턴값이 optional 임

optional https://www.daleseo.com/java8-optional-after/

get() 메소드는 비어있는 Optional 객체를 대상으로 호출할 경우, 예외를 발생시키므로 다음과 같이 객재 존재 여부를 bool 타입으로 반환하는 isPresent()라는 메소드를 통해 null 체크가 필요합니다.

Optional의 값을 get 할경우 isPresent()를 통해서 null 값을 체크 해야하는 데 이렇게 사용할 시 Optional을 사용하지 않는 것과 동일하게 됩니다. 그래서 orElseThrow 같이 null 일때 예외처리를 통해 해결하였습니다.

  • password Encoder 를 bean으로 등록해놓고 주입받아서 사용하면 어떨까요?

http://wonwoo.ml/index.php/post/1701

2019-08-21 [Spring-study] QnA 3단계 - 로그인 기반 개발

|

QnA 3단계 - 로그인 기반 개발

요구사항

  • 로그인 전 후 상태 충족

  • 로그인 후 질문 가능 하도록 + 수정, 삭제

  • question - user -> ManyToOne 관계 만들기

공부한 거

Exception 처리

ControllerAdvicce

  • 예외처리를 전역적으로 처리

  • ExceptionHandler

    @ExceptionHandler(value = LoginException.class)
      public String loginException(LoginException e) {
        return "redirect:/users/login/form";
      }
    

    예외 클래스 지정 후 메소드 작성

다음 단계에서

  • ErrorCode를 Enum 객체로 지정 후 Error Response 객체를 통한 유연한 처리
  • json으로 에러에 대한 message 작성 예정
  • https://cheese10yun.github.io/spring-guide-exception/

ManyToOne 매핑

User 와 Question 관계 = 1 : 다

  • 사용법

    @Entity
    public class Question {
        @ManyToOne
        @JoinColumn(foreignKey = @ForeignKey(name = "fk_question_writer"))
        private User writer;
        [...]
    }
    
    • writer의 User 객체로 매핑

Session (HttpSession session)

  • 저장하기 : session.setAttribute()
  • 불러오기 : session.getAttribute() (null 값 체크하기)

테스트 데이터 추가 방법

  • .sql 파일 만들고 쿼리문 작성
INSERT INTO USER (id, user_id, password, name, email) VALUES (1, 'hello', '1234', '자바지기', 'javajigi@slipp.net');
INSERT INTO USER (id, user_id, password, name, email) VALUES (2, 'test', '1234', '산지기', 'sanjigi@slipp.net');
  • User 객체 userId -> user_id

궁금증

예외 혹은 특정 조건에 충족될 때 특정 페이지로 이동하려면 어떻할까?

image

위 조건 식이 많이 중복 되어서 리팩토링 하고 싶은 데 return 때문에 하지 못하였다.

​ => throw 예외발생 후 exceptionHandler 를 통한 예외처리

리뷰 받은 내용

혁진이형

  • password 검사를 하는데 User 객체로 넘겨주는게 어색하지 않나요?

    • User을 일치여부를 확인하는 것으로 변경

      public boolean isSameUser(User other) {
          if (other == null) {
            return false;
          }
          
          if(userId != other.userId){
            return false;
          }
          
          return password.equals(other.getPassword());
        }
      
  • User와 Question 이 왜 DTO 인지 설명해보세요

  • Entity와 VO와 DTO의 차이점을 설명해주세요. 그리고 domain 이란 무엇인지 설명해주세요

    VO

    • 데이터 그 자체로 의미 있는 것을 담고 있는 객체(read only)
      • 사용 되는 값이 객체로 표현 되며, 값 변경이 없는 경우를 말함

    DTO

    • 전송되는 데이터의 컨테이너
      • 데이터의 전송을 위한 객체이며, 비지니스 로직까지 담아서 사용함
    • Request와 Response용 DTO는 View를 위한 클래스 자주 변경이 필요한 클래스

    Entity class

    • db의 테이블과 매칭될 클래스
      • Entity 클래스와 DTO 클래스를 분리하는 이유
        • 테이블과 매핑되는 Entity 클래스가 변경되면 여러 클래스에 영향을 끼치게 되는 반면 View와 통신하는 DTO 클래스(Request / Response 클래스)는 자주 변경되므로 분리해야 함.

    User와 Question은 기존에 비즈니스 로직에서 사용되고 변경이 필요하기 때문에 DTO로 정했습니다. domain에 대해 공부해보니 User, Question은 DB에 담길 Entity 클래스이기 때문에 여기서는 domain을 사용하는 게 맞는 것 같습니다. 참고

  • SessionUtils 는 유틸클래스이므로 프로젝트의 전 영역에서 사용될 수 있는 공용 클래스입니다. 따라서 여기에서는 세션에 저장된 유저를 가져오고, 세션에 유저를 저장하는 행위만 있으면 좋을 것 같은데, Question의 작성자까지 검증하는 행위까지 추가하는 것을 보니 앞으로 세션과 관련된 모든 로직이 여기에 생길까하는 우려가 됩니다. Util 클래스는 사용 되는 범위가 너무 넓으므로 변경이 거의 일어나지 않는 로직만 위치시켜야합니다. 중복을 줄이고자 좋은 고민을 하신 것 같은데 조금만 더 리팩토링 해보면 좋을 것 같아요

    • Util은 전역으로 처리 하기 때문에 util의 기능 범위를 판단 필요

2019-08-15 [Spring-study] QnA 2단계 - DB에 데이터 저장

|

QnA 2단계 - DB에 데이터 저장

공부한 거

프레임워크 와 라이브러리의 차이

  • 프레임 워크는 일반 적으로 이라고 말할 수 있다. 프로젝트 또는 애플리케이션을 작업을 할 때 더욱 편리하게 작업을 할 수 있는 환경을 제공 해주며 효율적인 개발을 위한 기본적인 뼈대라 말 할수있다. 반면에 라이브러리는 반복적으로 사용하는 코드를 미리 구현 해 놓고 가져다가 쓰는 도구로써의 기능을 한다고 말 할 수 있다.

H2 콘솔 접속

  • 접속 주소 : http://localhost:8080/h2-console

기타 설정 관련

  • nextstep 참고

DB에 저장할 User 클래스

  • User 클래스를 @Entity로 설정한다.

  • User 클래스에 대한 유일한 key 값을 @Id로 매핑한다. (primary key)

  • @Id에 @GeneratedValue 애노테이션을 추가하면 key가 자동으로 증가한다.

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
  • 각 필드를 테이블 칼럼과 매핑할 때는 @Column 애노테이션을 사용한다.

Repository 생성시

public interface UserRepository extends JpaRepository<User, Long> {

}
  • <User, Long> => 객체, Id 타입

  • findById, findByUserId,… 등 변수를 찾아주는 코드 구현시

    public interface UserRepository extends CrudRepository<User, Long> {
      User findByUserId(String userId);
    }
    

    메소드 명만 작성

h2 사용

  • finallAll(), findById()로 데이터 찾기

궁금 증

JpaRepository 와 CrudRepository 의 차이점

  • JpaRepository 는 PagingAndSortingRepository 와 CrudRepository 확장
  • PagingAndSortingRepository 와 CrudRepository 기능
  • CrudRepository = CRUD 기능 제공
  • PagingAndSortingRepository = 페이지 매김 및 정렬 레코드를 수행하는 메소드를 제공

  • JpaRepository = 페이지 매김 및 정렬 레코드를 수행하는 메소드를 제공

위에서 언급 한 상속 때문에 JpaRepository는 CrudRepository 및 PagingAndSortingRepository의 모든 기능을 갖습니다. 따라서 JpaRepository와 PagingAndSortingRepository가 제공하는 기능을 저장소에 필요로하지 않는다면 CrudRepository를 사용

2019-08-09 [Spring-study] QnA 1단계 - DB 없이 질문 기능

|

QnA 1단계 - DB 없이 질문 기능

공부한 내용

GET, POST 사용 기준

  • Get
    • Selcet 적인 성향
    • 서버에서 어떤 데이터를 가져와 보여줌 (서버의 값이나 상태를 바꾸지 않음)
  • Post
    • 서버의 값이나 상태를 바꾸기 위해서 사용
    • DB 저장이나 수정

HTTP 상태 코드

status code 단위별 의미

  • 2XX : 성공. 클라이언트가 요청한 동작을 수신하여 이해했고 승낙했으며 성공적으로 처리
  • 3XX : 라다이렉션 완료. 클라이언트는 요청을 마치기 위해 추가 동작이 필요함.
  • 4XX : 요청 오류. 클라이언트에 오류가 있음
  • 5XX : 서버 오류. 서버가 유효한 요청을 명백하게 수행하지 못했음

위키백과 - https://ko.wikipedia.org/wiki/HTTP_%EC%83%81%ED%83%9C_%EC%BD%94%EB%93%9C

VO 와 DTO 차이

VO

  • 데이터 그 자체로 의미 있는 것을 담고 있는 객체(read only)
    • 사용 되는 값이 객체로 표현 되며, 값 변경이 없는 경우를 말함

DTO

  • 전송되는 데이터의 컨테이너
    • 데이터의 전송을 위한 객체이며, 비지니스 로직까지 담아서 사용함

@AutoWired 란

  • Bean을 자동으로 주입해준다. (DI)
  • 같은 타입이 여러개일 때 -> @Qualifier() 이용
  • 혁진이형 리뷰 참고

Lombok

공식 문서 https://projectlombok.org/features/all>

어노테이션 기반으로 Getter, Setter ToString 등을 자동으로 생성해 주는 라이브러리

사용법

  • intelliJ plugin 에서 lombok 추가

  • dependency 추가

    <dependency>
    	<groupId>org.projectlombok</groupId>
    	<artifactId>lombok</artifactId>
    	<version>1.16.20</version>
    </dependency>
    

PUTMapping

html form 태그는 POST, GET 요청 밖에 안됨.

  • put, delete 를 사용하기 위해서는 <input type="hidden", name="_method", vlaue="put"> 추가

리뷰 받은 내용

혁진이형

  • Autowired 를 사용하지 않고 구현해보도록 한다

    • 처음에 Autowired 를 사용하지 말라고 한 의도는 다음과 같습니다.

      인강 들어서 아시겠지만 스프링에서 의존성 주입(DI)하는 방식으로 필드 주입, 생성자 주입, 세터 주입이 있습니다. 이 중에서 필드 주입으로 Autowired 를 많이들 사용하는데요, Spring 팀에서도 이제는 필드주입 자체를 지양하라고 명시하고 있습니다.

      애초에 필드만 가지고 객체를 주입하기 위해서는 java reflection을 사용해서 억지로 필드에 접근해서 객체를 할당해야합니다. (생성자, setter 없이 어떻게 객체가 할당될 수 있는지 생각해보시면 좋을 것 같네요) 이것은 POJO(순수한 자바 오브젝트)를 통해 개발을 할 수 있도록 지향하는 스프링의 프레임워크 철학에 위배되는 사항입니다. 또한 정상적인 방법으로 주입을 받지 않기 때문에 단위테스트가 어렵습니다. 그리고 리플렉션은 비용이 큽니다

      https://blog.marcnuri.com/field-injection-is-not-recommended/

      참고 글입니다.

      UserService나 UserDao 나 스프링 컴포넌트들은 모두 Bean으로 관리되는 싱글턴 패턴입니다. DI를 통한 의존성 주입 방식을 사용하는데요,

      @Autowired
      UserService(UserDao userdao){
      	this.userdao = userdao
      }
      

      이렇게 적어주면 자동으로 의존성 주입이 되었는데, 스프링 4.x 버전인가 3.x 버전부터는 Autowired 를 제거하고

      UserService(UserDao userdao){
      	this.userdao = userdao
      }
      

      이렇게 생성자만 적어주는 것만으로도 자동으로 의존성 주입이 가능해졌습니다.

      한마디로 결론은 Autowired 를 통한 필드 주입을 지양하고 생성자 주입을 사용하자 입니다. 필드 주입 대신 생성자 주입을 사용하는 이유

      @resource@Inject 같은 경우는 스프링에서 Autowired 기능을 추가했을 때 자바 진영에서 만든 애너테이션들입니다. 결국 Autowired를 따라한 것인데요, 이것들도 사용을 지양하는 것이 좋습니다.

  • update 동사를 지우고 @PutMapping으로 구현해보자

  • 로직이 컨트롤러에 노출되어 있음. 로직을 최소화시키자

  • lombok를 사용하여 getter setter 처리하자

  • get의 사용을 줄여보자.