728x90

사용배경:

모 회사에서 페이서비스를 만든다고 하자. 서로다른 고객 서로 다른 페이방식을 사용할것이다. 예컨대 , 네이버 페잇, 카카오페이, 유니온페이 등등.이때 일반적으로 if else 혹은 switch 로 고객타입을 체크하여 그에 맞는 페이방식을 사용하도록 코드를 작성할것이다.

그래서 한번 전략패턴으로 if 문 혹은 switch 문없이 처리해보겠다.

Base인터페이스 하나 만든다.

public interface BasePayment {
 
    /**
     * 기본 인터페이스
     * @param order
     * @return
     */
    PayResult pay(Order order);
 
}

Order 정보 클래스를 만든다.

@Data
public class Order {
 
    /**
     * 금액
     */
    private int amount;
 
    /**
     * 페이유형
     */
    private String paymentType;
 
}

Return 타입클래스도 만든다.

@Data
@AllArgsConstructor
public class PayResult {
 
    /**
     * 처리결과
     */
    private String result;
 
}

이제 Base 인터페이스 기준으로 각종? 구현 클래스를 한번 만들어 보자. (카카오 페이, 네이버 페이 등등 )

@Service("NaverPay")
public class NaverPay implements BasePayment {
 
    @Override
    public PayResult pay(Order order) {
        return new PayResult("네이버 페이 결제 성공!");
    }
 
}
@Service("KakaoPay")
public class KakaoPay implements BasePayment {
 
    @Override
    public PayResult pay(Order order) {
        return new PayResult("카카오페이 결제 성공!");
    }
 
}

뭐 유니온 페이도 한번 만들어본자 ^^

@Service("UnionPay")
public class UnionPay implements IPayment {
 
    @Override
    public PayResult pay(Order order) {
        return new PayResult("인련페이 지불성공");
    }
 
}

이제 컨트롤러를 하나 만들어보자

@RestController
public class PayService {
 
    @Autowired
    private ApplicationContext applicationContext;
 
    /**
     * 컨트롤러
     * @param amount
     * @param paymentType
     * @return
     */
    @RequestMapping("/pay")
    public PayResult pay(@RequestParam("amount") int amount,
                    @RequestParam("paymentType") String paymentType) {
        Order order = new Order();
        order.setAmount(amount);
        order.setPaymentType(paymentType);
 
        // paymentType으로 bean을 컨테이너로 부터 가져올수 있지
        IPayment payment = applicationContext.getBean(order.getPaymentType(), BasePayment.class);
 
        // 지불시작
        PayResult payResult = payment.pay(order);
 
        return payResult;
    }
 
}

끝!

728x90

프로젝트 관리하다가 외주업체에 맞겼는데 if..else로 도배를 해놓았던것이다.
음 개발자입문할때 누군가가 그러더라 "개발자는 if문,for문만 알아도 개발을 할수 있다" 고. 지금와서 생각해보니 틀린말은 아니다.
그런데 우리는 사람이다. 사람은 항상 생각하고 불편함을 해결하고 복잡한것보다 쉬운것을 추구한다.
그래서 나도 오늘 좀 생각없이 개발하는 아재 개발자들한테 if..else를 줄일수 있는 방법을 말해보려고 한다.
이미 알고 있는 고수분들은 그냥 잘난척하는 내앞을 지나가 주라!
일단 ...이런 코드를 많이 봤지?

@RestController
@PostMappting("/create")
public CommonResult testController(@RequestBody TestDto testDto ){
	
    if(StringUtils.isEmpty(testDto.getName()){
    	return CommonResult.validateFailed("이름이 비었음!");
    }
    if(testDto.getSort() == null || testDto.getSort() < 0){
    	return CommonResult.validateFailed("0 보다 작을수 없음!");
    }
    int count = testService.create(testDto);
    if(count ==1){
    	return CommonResult.success(testDto);
    }
}

Springboot 에서 Hibernate Validator library를 이용하여 검증을 하면 편하다.
1. library 추가

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

2. 객체를 만든다.

@Data
public class Student {

    @NotBlank(message = "이름은 필수잆력")
    private String name;
    @NotNull(message = "나이는 필수잆력")
    @Range(min = 1,max =50, message = "나이 범위1-50")
    private Integer age;
    @NotEmpty(message = "성적은 필수잆력")
    private List<Double> scores;
}

3. post 요청 controller 를 만든다.

@RestController
@RequestMapping("/student")
public class StudentController {

    @PostMapping("/add")
    public Rest<?> addStudent(@Valid @RequestBody Student student) {
        return RestBody.okData(student);
    }

요청결과:

POST /student/add HTTP/1.1
Host: localhost:8888
Content-Type: application/json

{
    "name": "felord.cn",
    "age": 77,
    "scores": [
        55
    ]
}

4. get 요청 controller 를 만든다.

@GetMapping("/get")
public Rest<?> getStudent(@Valid Student student) {
    return RestBody.okData(student);
}

요청결과:

GET /student/get?name=blake.com&age=12 HTTP/1.1
Host: localhost:8888

5. 커스텀 어노테이션
주위 깊게 보았으면 발견했을듯! 바로 Student 객체에 @NotNull + @Range 어노테이션을 겹쳐서 사용했다.

@NotNull(message = "나이필수 입력")
@Range(min = 1,max =50, message = "나이범위 1-50")
private Integer age;

원인은 @Range 가 공백값에 대한 제약을 할수 없다는것, 값이 있을때만 제약대상이다. 이처럼 어노테이션을 여러개 사용해야 하는경우가 가끔있는데 이럴때 이런 어노테이션들을 하나로 묶는것도 방법이긴 하다.

import org.hibernate.validator.constraints.Range;

import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.ReportAsSingleViolation;
import javax.validation.constraints.NotNull;
import javax.validation.constraintvalidation.SupportedValidationTarget;
import javax.validation.constraintvalidation.ValidationTarget;
import java.lang.annotation.*;


@Constraint(
        validatedBy = {}
)
@SupportedValidationTarget({ValidationTarget.ANNOTATED_ELEMENT})
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD,
        ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR,
        ElementType.PARAMETER, ElementType.TYPE_USE})
@NotNull
@Range(min = 1, max = 50)
@Documented
@ReportAsSingleViolation
public @interface Age {
    // message 필수
    String message() default "나이는 필수입력,범위는 1-50 ";

    // 선택
    Class<?>[] groups() default {};

    // 선택
    Class<? extends Payload>[] payload() default {};
}

혹은 상태와 같은 불변의 뭔가를 따로 관리해야 된다고 하면 enum클래스를 만들어서 사용하면 좋다.

public enum Colors {

    RED, YELLOW, BLUE

}

public class ColorConstraintValidator implements ConstraintValidator<Color, String> {
    private static final Set<String> COLOR_CONSTRAINTS = new HashSet<>();

    @Override
    public void initialize(Color constraintAnnotation) {
        Colors[] value = constraintAnnotation.value();
        List<String> list = Arrays.stream(value)
                .map(Enum::name)
                .collect(Collectors.toList());
        COLOR_CONSTRAINTS.addAll(list);

    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return COLOR_CONSTRAINTS.contains(value);
    }
}

@Constraint(validatedBy = ColorConstraintValidator.class)
@Documented
@Target({ElementType.METHOD, ElementType.FIELD,
        ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR,
 
        ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Color {
    // 에러 메시지
    String message() default "규격에 맞는 않음.";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    // 유형
    Colors[] value();
}

제약이 잘 걸리는지 함 테스트 해보자!

@Data
public class Param {
    @Color({Colors.BLUE,Colors.YELLOW})
   private String color;
}

요청하기

GET /student/color?color=CAY HTTP/1.1
Host: localhost:8888

결과:
BindException 발생한다!
CAY 는 존재하지 않기때문. 그래서 BLUE, YELLOW, RED를 넣으면 리턴이 정상이다.


트러블슈팅:

@GetMapping("/color")
public Rest<?> color(@Valid @Color({Colors.BLUE,Colors.YELLOW}) String color) {
    return RestBody.okData(color);
}

// 혹은

@GetMapping("/rest/{color}")
public Rest<?> rest(@Valid @Color({Colors.BLUE, Colors.YELLOW}) @PathVariable String color) {
    return RestBody.okData(color);
}

// 혹은

@PostMapping("/batchadd")
public Rest<?> batchAddStudent(@Valid @RequestBody List<Student> student) {
    return RestBody.okData(student);
}

이처럼 클래스아닌 method 에 적용하연 제약기능이 작동하지 않는다. 구글링해보면 클래스에 적용해야 되지 메서드에 적용하면 안된다는것을 할수 있다. (직접 검색해 보시길!)
마지막으로
어노테이션을 겹쳐서 사용시 제약이 걸리지 않는 문제 도 있음!

@Data
 
public class Student {

    @NotBlank(message = "이름 필수입력")
    private String name;
    @Age
    private Integer age;
    @NotEmpty(message = "성적 필수입력")
    private List<Double> scores;
    @NotNull(message = "학교 공백일수 없음")
    private School school;
}


@Data
public class School {
    @NotBlank(message = "이름 공백일수 없음")
    private String name;
    @Min(value = 0,message ="나이는 0보다 커야 됨." )
    private Integer age;
}

이런 구조일때 get 요청시 문제 없으니, post 요청시 School 에 대한 제약기능이 작동안된다.
이때 우리는 @Valid 어노테이션을 붙여주면 된다.

@Data
public class Student {
    
    @NotBlank(message = "이름 필수입력")
    private String name;
    @Age
    private Integer age;
    @NotEmpty(message = "성적 필수입력")
    private List<Double> scores;
    @Valid
    @NotNull(message = "학교 공백일수 없음")
    private School school;
}

참고:

// message는 오류 메시지를 정의하는 곳

// null만 허용
@Null(message="")

// null 허용 X,  "", " "는 허용
@NotNull(message="")

 // null, "" 허용 X,  " "는 허용
@NotEmpty(message="")

// null, "", " " 허용 X
@NotBlank  (message="")

// 데이터의 사이즈(최소 길이, 최대 길이) 설정
@Size(min=, max= ,message="")  

// 정규식을 이용해서 검사
@Pattern(regexp = ,message="")  

// value 이하의 값만 허용
@Max(value = ,message="")

// value 이상의 값만 허용
@Min(value = ,message="") 

// 값을 양수만 허용
@Positive(message="")

// 값을 양수와 0만 허용
@PositiveOrZero(message="")  

// 값을 음수만 허용
@Negative(message="")   

// 값을 음수와 0만 허용
@NegativeOrZero(message="")  

// 현재보다 미래의 날짜만 허용
@Future(message="")   

// 현재보다 과거의 날짜만 허용
@Past(message="")   

// True일 때만 허용(null 체크 X)
@AssertTrue(message="")

// False일 때만 허용(null 체크 X)
@AssertFalse(message="")

끝!

728x90

request.getContextPath() : 프로젝트 path 만 가져온다

return : "/board";

request.getRequestURI() : 프로젝트 파일 경로까지 가져온다.

String url = request.getRequestURI.split("/");
String fName = url[url.length -1]; 
// fName = list.jsp
// 전체 URL에서 port 번호 다음부터 마지막 문자열까지 반환 함)
// ex : http://localhost:8989/board/list.jsp

return : "/board/list.jsp";

request.getRequestURL() : 전체 경로를 가져온다.

//ex: http://localhost:8989/board/list.jsp

return : "http://localhost:8080/board/list.jsp";

requestServletPath() : 파일명만 가져옴

// ex: http://localhost /list.jsp:8989/board/list.jsp
return : "/list.jsp";

request.getRealPath() : 서버 또는 로컬의 웹 애플리케이션 서버의 docBase 설정값을 반환함 (절대 경로 가지오기)

// ex: http://localhost:8989/board/list.jsp 

return : "/project/demo/board"

뭐 오래된 글이긴 하지만 꽤 유용해 보인다 ^^ 

728x90

Mysql

XML

<bean id="dataSource"  class="org.springframework.jdbc.datasource.DriverManagerDataSource">
  <property name="driverClassName" value="com.mysql.jdbc.Driver" />
  <property name="url" value="jdbc:mysql://localhost:3306/mydb?characterEncoding=EUCKR" />
  <property name="username" value="abcd" />
  <property name="password" value="pppw" />
 </bean>

Oracle

XML

<bean id="dataSource"  class="org.springframework.jdbc.datasource.DriverManagerDataSource">
  <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
  <property name="url" value="jdbc:oracle:thin:@localhost:1521:orcl />
  <property name="username" value="abcd" />
  <property name="password" value="pppw" />
 </bean>

+ Recent posts