(1) @InitBinder 이란?

  • WebDataBinder를 초기화 하는 메소드를 지정할 수 있는 설정을 제공합니다.
  • 특정 컨트롤러에서 바인딩 또는 검증 설정을 변경하고 싶을 때 사용합니다.
  • 모든 요청 전 InitBinder를 선언한 메소드가 실행됩니다.

(2) 사용법

  • Controller
    @RestController
    public class MemberController {
    
    	private final MemberService memberService;
    	private final MemberLoginRequestValidator memberLoginRequestValidator;
        
        @Autowired
        public MemberController(MemberService memberService, MemberLoginRequestValidator memberLoginRequestValidator){
        	this.memberService = memberService;
            this.memberLoginRequestValidator = memberLoginRequestValidator;
        }
    
        //memberLoginRequestDto 객체로 요청이 들어올 경우 memberLoginRequestValidator를 우선 실행
        @InitBinder("memberLoginRequestDto")
        public void loginBinder(WebDataBinder webDataBinder){
            webDataBinder.addValidators(memberLoginRequestValidator);
        }
        
        //로그인
        @PostMapping("/api/member/login")
        public ResponseEntity<Map<String,String>> memberLogin(@Valid @RequestBody MemberLoginRequestDto memberLoginRequestDto, HttpServletResponse httpServletResponse){
    
            return ResponseEntity.ok(memberService.memberLogin(memberLoginRequestDto, httpServletResponse));
        }
    }
  • Validator
    @Component
    public class MemberLoginRequestValidator implements Validator {
    
        private final MemberRepository memberRepository;
        private final PasswordEncoder passwordEncoder;
    
        public MemberLoginRequestValidator(MemberRepository memberRepository, PasswordEncoder passwordEncoder){
            this.memberRepository = memberRepository;
            this.passwordEncoder = passwordEncoder;
        }
    
        @Override
        public boolean supports(Class<?> clazz){
            return clazz.isAssignableFrom(MemberLoginRequestDto.class);
        }
    
        @Override
        public void validate(Object object, Errors errors){
            MemberLoginRequestDto memberLoginRequestDto = (MemberLoginRequestDto) object;
    
            if(!memberRepository.existsById(memberLoginRequestDto.getEmail())){
                errors.rejectValue("email", "invalid.email", "존재하지 않는 email입니다.");
            } else {
                MemberEntity memberEntity = memberRepository.getById(memberLoginRequestDto.getEmail());
                if(!passwordEncoder.matches(memberLoginRequestDto.getPassword(), memberEntity.getPassword())) {
                    errors.rejectValue("password","invalid.password", "패스워드가 일치하지 않습니다.");
                }
            }
        }



(1) @Data 어노테이션이란?

  • @Data = @toString + @getter + @setter + @RequiredArgsConstructor + @EqualsAndHashCode
  • @toString - toString() 메서드를 생성한다. @ToString(exclude = {"제외값"})으로 제외시키고 싶은 값을 설정할 수 있다.
  • @getter/setter - getter(), setter() 메서드를 생성한다.
  • @RequiredArgsConstructor - 초기화 되지 않은 모든 final 필드, @NonNull과 같이 제약조건이 설정되어있는 모든 필드들에 대한 생성자를 자동으로 생성한다.
  • @EqualsAndHashCode - equals(), hashCode() 메서드를 생성한다.

(2) 문제점

  • @RequiredArgsConstructor 어노테이션으로 문제가 발생할 수 있다. 예를 들어 인해 두 개의 타입 인스턴스 멤버를 선언한 상황에서 개발자가 선언된 인스턴스 멤버의 순서를 바꾸면, 개발자도 인식하지 못하는 사이에 lombok이 생성자의 파라미터 순서를 필드 선언 순서에 따라 변형하게 된다. 이때, IDE가 제공해주는 리팩토링은 전혀 동작하지 않고, 두 필드가 동일 타입이기 때문에 기존 소스에서도 오류가 발생하지 않아 아무런 문제없이 동작하는 것으로 보이지만, 실제로 입력된 값이 바뀌어 들어가는 상황이 발생한다.

출처 - https://velog.io/@leyuri/Spring-boot-JPA-%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98-Entity-Table-Column-Id-Lombok

'Study > Spring' 카테고리의 다른 글

[Spring] @InitBinder  (0) 2021.08.08
[Spring] 영속성 컨텍스트 (Persistence Context)  (0) 2021.06.30
[Spring Boot] Spring 프레임워크  (2) 2021.06.26
[Spring Boot] 유효성 검사(Validation)  (0) 2021.04.11

(1) 영속성 컨텍스트 (Persistence Context)란?

  • 영속성 컨텍스트는 엔티티를 영구 저장하는 환경입니다.
  • 영속성 컨텍스트는 눈에 보이지 않는 논리적인 개념입니다.

(2) 영속성 컨텍스트 (Persistence Context) 특징

  • @Id 어노테이션을 통해 엔티티를 식별합니다.
  • 1차 캐시가 있습니다. Entity가 DB에 저장되기 전에 사용되는 공간인데, 반대로 DB를 조회하더라도 1차캐시에 저장하게 됩니다. 따라서 같은 Entity를 읽고자 할 때 빠른 읽기 기능을 제공하고 부하를 줄여줍니다. DB에 저장할 때 역시 1차 캐시에 저장됨으로써 중간에 수정할 사항이 있다면 UPDATE쿼리를 사용하지 않고도 INSERT쿼리만으로 바로 저장할 수 있습니다.
  • 위 처럼 캐시에 있는 것과 실제 DB에 존재하는 것이 동일하기 때문에 객체의 동일성을 보장합니다.
  • 트랜잭션을 지원하는 쓰기지연 기능이있습니다. 즉 값을 변경하자마자 바로 DB에 반영하는 것이 아니라, 영속성 컨텍스트 내부의 SQL 저장소에 생성 쿼리를 저장 해둡니다. 이 후 commit을 하게 되면 저장해두었던 쿼리를 데이터베이스에 보냅니다.
  • 엔티티 매니저는 find(조회), persist(추가), remove(삭제)만 있으며 update() 메서드는 존재하지 않는데 그 이유는 변경 감지(dirty checking)를 하기 때문입니다. 변경 감지가 가능한 이유는 1차 캐시에 최초로 저장될 때 그 상태를 스냅샷을 해 두었다가 영속성 컨텍스트와 DB사이의 동기화가 이루어지는 flush 시점에서 스냅샷과 현재 엔티티의 상태를 비교하여 엔티티가 변경되었다면 UPDATE 쿼리를 실행합니다.

참조 - http://blog.neonkid.xyz/233, https://victorydntmd.tistory.com/207

'Study > Spring' 카테고리의 다른 글

[Spring] @InitBinder  (0) 2021.08.08
[Spring] @Data 어노테이션  (0) 2021.06.30
[Spring Boot] Spring 프레임워크  (2) 2021.06.26
[Spring Boot] 유효성 검사(Validation)  (0) 2021.04.11

(1) Spring 프레임워크란?

  • 자바 플랫폼을 위한 오픈소스 애플리케이션 프레임워크로서 엔터프라이즈급 애플리케이션을 개발하기 위한 모든 기능을 종합적으로 제공하는 경량화된 솔루션입니다. Spirng Framework는 경량 컨테이너로 자바 객체를 담고 직접 관리합니다. 객체의 생성 및 소멸 그리고 라이프 사이클을관리하며 언제든 Spring 컨테이너로 부터 필요한 객체를 가져와 사용할 수 있습니다. 이는 Spirng이 IOC 기반의 Framework임을 의미합니다.
  • 엔터프라이즈급 개발이란 뜻대로만 풀이하면 기업을 대상으로 하는 개발이라는 말입니다. 즉, 대규모 데이터 처리와 트랜잭션이 동시에 여러 사용자로 부터 행해지는 매우 큰 규모의 환경을 엔터프라이즈 환경이라 일컫습니다.

(2) Spring 프레임워크의 주요 특징

    • IOC(제어의 반전)
      • 스프링의 가장 중요하고 핵심적인 기능으로서 자바의 반영(reflection)을 이용해서 객체의 생명주기를 관리하고 의존성 주입(Dependency Injection)을 통해 각 계층이나 서비스들간의 의존성을 맞춰준다. 이러한 기능들은 주로 환경설정을 담당하는 XML 파일에 의해 설정되고 수행된다.
    • AOP(관점 지향 프로그래밍)
      • 대부분 소프트웨어 개발 프로세스에서 사용하는 방법은 OOP(Object Oriented Programming) 입니다. OOP는 객체지향 원칙에 따라 관심사가 같은 데이터를 한곳에 모아 분리하고 낮은 결합도를 갖게하여 독립적이고 유연한 모듈로 캡슐화를 하는 것을 일컫습니다. 하지만 이러한 과정 중 중복된 코드들이 많아지고 가독성, 확장성, 유지보수성을 떨어 뜨립니다.
      • AOP에서는 핵심기능과 공통기능을 분리시켜 핵심 로직에 영향을 끼치지 않게 공통기능을 끼워 넣는 개발 형태 이며 이렇게 개발함에 따라 무분별하게 중복되는 코드를 한 곳에 모아 중복 되는 코드를 제거 할 수 있어지고 공통기능을 한 곳에 보관함으로써 공통 기능 하나의 수정으로 모든 핵심기능들의 공통기능을 수정 할 수 있어 효율적인 유지보수가 가능하며 재활용성이 극대됩니다. 물론 AOP로 만들 수 있는 기능은 OOP로 구현 할 수 있는 기능이지만 Spring에서는 AOP를 편리하게 사용 할 수 있도록 이를 지원하고 있습니다.
    • POJO(Plain Old Java Object)
      • 이전 EJB(Enterprise JavaBeans)는 확장 가능한 재사용이 가능한 로직을 개발하기 위해 사용 되었었는데 EJB는 한가지 기능을 위해 불필요한 복잡한 로직이 과도하게 들어가는 단점이 있었습니다. 그래서 다시 조명을 받은게 POJO입니다. POJO는 gettet/setter를 가진 단순 자바 오브젝트로 정의를 하고 있습니다. 이러한 단순 오브젝트는 의존성이 없고 추후 테스트 및 유지보수가 편리한 유연성의 장점을 가집니다. 이러한 장점들로 인해 객체지향적인 다양한 설계와 구현이 가능해지고 POJO의 기반의 Framework가 조명을 받고 있습니다.
    • MVC 패턴
      • 스프링은 웹 프로그램밍 개발 시 거의 표준적인 방식인 Spring MVC라 불리는 모델-뷰-컨트롤러(MVC) 패턴을 사용한다. DispatcherServlet이 Controller 역할을 담당하여 각종 요청을 적절한 서비스에 분산시켜주며 이를 각 서비스들이 처리를 하여 결과를 생성하고 그 결과는 다양한 형식의 View 서비스들로 화면에 표시될 수 있다.
      • Model
        • Model에서는 데이터처리를 담당하는 부분입니다. Model부분은 Serivce영역과 DAO영역으로 나누어지게 되고 여기서 중요한 것은 Service 부분은 불필요하게 HTTP통신을 하지 않아야하고 request나 response와 같은 객체를 매개변수로 받아선 안된다. 또한 Model 부분의 Service는 view에 종속적인 코드가 없어야 하고 View 부분이 변경되더라도 Service 부분은 그대로 재사용 할 수 있어야 한다.Model에서는 View와 Controller 어떠한 정보도 가지고 있어서는 안된다.
      • View
        • View는 사용자 Interface를 담당하며 사용자에게 보여지는 부분입니다. View는 Controller를 통해 모델에 데이터에 대한 시각화를 담당하며 View는 자신이 요청을 보낼 Controller의 정보만 알고 있어야 하는 것이 핵심이다. Model이 가지고 있는 정보를 저장해서는 안되며 Model, Controller에 구성 요소를 알아서는 안된다.
      • Controller
        • Controller에서는 View에 받은 요청을 가공하여 Model(Service 영역)에 이를 전달한다. 또한 Model로 부터 받은 결과를 View로 넘겨주는 역할을 합니다. Controller에서는 모든 요청 에러와 모델 에러를 처리하며 View와 Controller에 정보를 알고 있어야한다. Model과 View의 정보에 대해 알고 있어야한다.
    • 트랜잭션 지원
      • 데이터베이스를 연동하여 사용할 때 발생할 수 있는 문제를 해결하기 위해 트랜잭션 처리를 해주어야 한다. 스프링에서는 이러한 트랜잭션 처리를 어노태이션이나 xml로 설정할 수 있도록 지원해준다.
    • 일괄처리
      • 스프링은 특정 시간대에 실행하거나 대용량의 자료를 처리하는데 쓰이는 일괄 처리(Batch Processing)을 지원하는 배치 프레임워크를 제공한다. 기본적으로 스프링 배치는 Quartz 기반으로 동작한다.

참조 - https://ko.wikipedia.org/wiki/%EC%8A%A4%ED%94%84%EB%A7%81_%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC, https://khj93.tistory.com/entry/Spring-Spring-Framework%EB%9E%80-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90-%ED%95%B5%EC%8B%AC-%EC%A0%95%EB%A6%AC

  •  

'Study > Spring' 카테고리의 다른 글

[Spring] @InitBinder  (0) 2021.08.08
[Spring] @Data 어노테이션  (0) 2021.06.30
[Spring] 영속성 컨텍스트 (Persistence Context)  (0) 2021.06.30
[Spring Boot] 유효성 검사(Validation)  (0) 2021.04.11

(1) 유효성 검사(Validation) 란?

  • 어떤 데이터의 값이 유효한지, 타당한지 확인하는 것을 의미합니다.
  • 보안적인 측면에서 올바르지 않는 데이터가 서버로 전송되거나 DB에 전송되지 않도록 하는 것입니다.

(2) 관련 어노테이션

  • JSR-303 Validator
    • @AssertFalse - 거짓인가?
    • @AssertTrue - 참인가?
    • @Max - 지정 값 이하인가?
    • @Min - 지정 값 이상인가?
    • @DecimalMax - 지정 값 이하 실수인가?
    • @DecimalMin - 지정 값 이상 실수인가?
    • @NotNull - Null이 아닌가?
    • @Null - Null 인가?
    • @Digits (integer=, fraction=) - 대상 수가 지정된 정수, 소수 자리 수 이내인가?
    • @Pattern(regex=,flag=) - 정규식을 만족 하는가?
    • @Future - 미래날짜인가?
    • @Past - 과거 날짜인가?
    • @Size(min=, max=) - 문자열, 배열 등의 크기가 지정크기를 만족 하는가?
  • Hibernate
    • @NotEmpty - Empty 값이 아닌가?
    • @Email - 이메일 형식인가?
    • @URL - URL 형식인가?
    • @Length(min=, max=) - 문자열의 길이가 min과 max 사이인가?
    • @Range(min=, max=) - 숫자 범위가 min과 max 사이인가?

(3) 사용법

  • build.gradle
dependencies {
	compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    
    // https://mvnrepository.com/artifact/org.json/json
    implementation group: 'org.json', name: 'json', version: '20160810'
    
	implementation 'org.springframework.boot:spring-boot-starter-validation:2.3.3.RELEASE'
}
  • DTO
    @NotNull
    @NotBlank(message = "이메일 입력은 필수입니다.")
    @Email(message = "이메일 형식으로 입력해 주세요.")
    private String email;

    @NotNull
    @NotBlank(message = "비밀번호 입력은 필수입니다.")
    private String password;
  • Controller
    //회원 가입
    @PostMapping("/api/user/signup")
    public Object registerUsers(@Valid @RequestBody UserSignupRequestDto userSignupRequestDto){

    }
  • ErrorCode
import lombok.Getter;

public enum ErrorCode {

    NOT_NULL("ERROR_CODE_NOT_NULL","필수값이 누락되었습니다.")
    , MIN_VALUE("ERROR_CODE_MIN_VALUE", "최소값보다 커야 합니다.")
    , PATTERN("ERROR_CODE_PATTERN","값 형식이 다릅니다.")
    , NOT_BLANK("ERROR_CODE_NOT_BLANK","필수값이 누락되었습니다.")
    , EMAIL("ERROR_CODE_EMAIL","이메일 형식이 아닙니다.")
    ;

    @Getter
    private String code;

    @Getter
    private String description;

    ErrorCode(String code, String description) {
        this.code = code;
        this.description = description;
    }
}
  • ErrorResponse
import lombok.Getter;
import lombok.Setter;

@Setter
@Getter
public class ErrorResponse {

    private String code;

    private String description;

    private String errorMessage;

    public ErrorResponse(String code, String description) {
        this.code = code;
        this.description = description;
    }

    public ErrorResponse(String code, String description, String errorMessage) {
        this.code = code;
        this.description = description;
        this.errorMessage = errorMessage;
    }
}
  • ExceptionController
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import javax.servlet.http.HttpServletRequest;
import java.util.Objects;

@Slf4j
@ControllerAdvice
public class ExceptionController {

    /**
     * @valid  유효성체크에 통과하지 못하면  MethodArgumentNotValidException 이 발생한다.
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> methodValidException(MethodArgumentNotValidException e, HttpServletRequest request){
        log.warn("MethodArgumentNotValidException 발생!!! url:{}, trace:{}",request.getRequestURI(), e.getStackTrace());
        ErrorResponse errorResponse = makeErrorResponse(e.getBindingResult());
        return new ResponseEntity<ErrorResponse>(errorResponse, HttpStatus.BAD_REQUEST);
    }

    private ErrorResponse makeErrorResponse(BindingResult bindingResult){
        String code = "";
        String description = "";
        String errorMessage = "";

        //에러가 있다면
        if(bindingResult.hasErrors()){
            //DTO에 설정한 meaasge값을 가져온다
            errorMessage = bindingResult.getFieldError().getDefaultMessage();

            //DTO에 유효성체크를 걸어놓은 어노테이션명을 가져온다.
            String bindResultCode = bindingResult.getFieldError().getCode();

//            switch (bindResultCode){
            switch (Objects.requireNonNull(bindResultCode)){
                case "NotNull":
                    code = ErrorCode.NOT_NULL.getCode();
                    description = ErrorCode.NOT_NULL.getDescription();
                    break;
                case "NotBlank":
                    code = ErrorCode.NOT_BLANK.getCode();
                    description = ErrorCode.NOT_BLANK.getDescription();
                    break;
                case "Min":
                    code = ErrorCode.MIN_VALUE.getCode();
                    description = ErrorCode.MIN_VALUE.getDescription();
                    break;
                case "Pattern":
                    code = ErrorCode.PATTERN.getCode();
                    description = ErrorCode.PATTERN.getDescription();
                    break;
                case "Email":
                    code = ErrorCode.EMAIL.getCode();
                    description = ErrorCode.EMAIL.getDescription();
                    break;
            }
        }

        return new ErrorResponse(code, description, errorMessage);
    }
}

 

  • Controller에서 @Valid 어노테이션이 붙은 값이 넘어왔을때 DTO에서 지정해준 조건에 맞지 않는다면 ExceptionController의 makeErrorResponse 함수가 작동하게 되고, 프론트엔드로 ErrorCode에서 지정해준 값과 DTO에 지정해준 Message가 Response Body에 담겨 프론트엔드로 응답하게 됩니다.

'Study > Spring' 카테고리의 다른 글

[Spring] @InitBinder  (0) 2021.08.08
[Spring] @Data 어노테이션  (0) 2021.06.30
[Spring] 영속성 컨텍스트 (Persistence Context)  (0) 2021.06.30
[Spring Boot] Spring 프레임워크  (2) 2021.06.26

+ Recent posts