반응형
반응형

application/json 헤더를 지정하여 WebAPI를 통해 들어오는 Json Request Body를 그대로 Broker에 전달해야하는 API를 만들 일이 생겼다.


전달하는 것까지는 좋은데, 최소한의 검증 (Null, Empty Check나 integer의 경우 마이너스 값 체크) 정도는 해야되겠다 싶어서, @Valid를 이용해보기로 하였다.


일단 Model선언은 아래와 같이 했다.

public class TestLog {
	@Min(1)
	private int id;
	
	@NotNull
	@NotEmpty	
	private String description;
	
	@NotNull
	@NotEmpty
	private String now;
}


@Min, @NotNull 의 경우 기본적으로 java에서 제공하지만, @NotEmpty같은 경우는 hibernate-validator 의존성을 추가해주어야 한다.  (http://hibernate.org/validator/)


위와 같이 Model을 만든 후 Controller의 API에, @Valid만 붙여주면 된다.

@PostMapping("/test_log")
public Result testLog(@RequestBody @Valid TestLog testLog) {
	return "ok";
}


이제 Body로 들어오는 파라미터가 id는 1보다 작은 경우, description, now는 비어있거나 Null인 경우 400에러와 함께, org.springframework.web.bind.MethodArgumentNotValidException를 뱉어내게 된다.


클라 입장에서 받는 Response Body는 아래와 같이 생겼다.

{
	"timestamp": 1494829780576,
	"status": 400,
	"error": "Bad Request",
	"exception": "org.springframework.web.bind.MethodArgumentNotValidException",
	"errors": [{
		"codes": ["Min.testLog.id", "Min.id", "Min.int", "Min"],
		"arguments": [{
			"codes": ["testLog.id", "id"],
			"arguments": null,
			"defaultMessage": "id",
			"code": "id"
		}, 1],
		"defaultMessage": "반드시 1보다 같거나 커야 합니다.",
		"objectName": "testLog",
		"field": "id",
		"rejectedValue": 0,
		"bindingFailure": false,
		"code": "Min"
	}],
	"message": "Validation failed for object='testLog'. Error count: 1",
	"path": "/api/test_log"
}


기본적으로 위와 같은 Json 형태로 Response를 주기 때문에, 클라 입장에서 공통으로 Body를 parsing하여 사용하려면, 서버 단에서 해당 Exception을 핸들링하여 별도로 아래와 같이 예외처리를 해주어야 한다. 

@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Result handleMethodArgumentException(MethodArgumentNotValidException e) {	
        return "fail";
}


반응형
,
반응형

3rd Party API를 jquery 단에서 여러번 호출해야 하는 일이 있는데, network latency 등 여러가지 이슈가 있기 때문에,

front단에서 한 번에 묶어서 보내고, back단에서 처리되게끔 하다보니, jquery 단에서 array형태로 묶어서,

back단(Spring)에서 List형태의 Model로 받아서 처리하니 깔끔하게 정리가 되어서, 이 방법에 대해 정리한다.


[Jquery]

var arr = [{"a":1, "b":2}, {"a":2, "b":4}];

$.ajax({
    url: 'url',
    , type: 'post'
    , dataType: 'json'   // 데이터 타입을 Json으로 변경
    , contentType: 'application/json'   // Content-Type을 Json으로 변경
    , data: JSON.stringify(arr)    // JSON String으로 전환하여 보낸다.
    , success: function(response) {
    }
});


위와 같이 전송을 하면, 서버에서는 javascript의 array를 Json Array로 인식을 하기 때문에, 

application/json 기반의 서비스라면, List Model로 매핑을 할 수가 있다. (Spring Boot가 기본 MessageConverter가 마침 Json이어서 이렇게 사용했다.)


[Spring Boot]

...
@Getter
@Setter
@ToString
public class Abc {
    Abc() { }

    private int a;
    private int b;
}

@PostMapping("url")
public void url(@RequestBody List abcs) {
    abcs.stream().forEach(abc -> {
        System.out.println(abc.toString());
    });
}
...


원하는 형태로 List로 잘 매핑이 된다.

반응형
,
반응형

TableName을 UpperCase로 지은 경우, @Table에다가 UpperCase로 똑같이 입력을 해줘도,

validate 단계에서 missing table 오류를 뱉는 버그(?)가 있었다.


이런 경우 아래와 같이 네이밍 정책을 변경해주면, 해결이 된다.


spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl


반응형
,
반응형

spring에는 많은 어노테이션들이 있죠.

 

그 중에서 편하게 사용할 수 있는 ModelAttribute에 대해 알아보도록 합시다.

 

파라미터를 넘길 때 보통은 아래와 같이 사용하는게 일반적이죠.

 

@RequestMapping("/test/abc")
@ResponseBody
public Object test(@RequestParam(value="a") String a,
                     @RequestParam(value="b") int b) {
   return "";
}


 

하지만 저것을 Model을 이용하여 한번에 넘길 수 있는 방법이 있답니다.

 

바로 ModelAttribute!!

 

일단 Model을 하나 만들어봅시다.

 

public Class testClass {
    String a;
    int b;
}


 

모델을 만들었으면 아래와 같이 사용합니다.

 

@RequestMapping("/test/abc")
@ResponseBody
public Object test(@ModelAttribute testClass test) {
   return "";
}


 

testClass에 멤버로 String a, int b가 있기 때문에,

파라미터로 a, b에 값을 담아서 보내면, test라는 클래스변수에 자동으로 값이 세팅되는 편리한 기능입니다.

 

게시판 같은 경우 Model단위로 파라미터를 넘기고 받는 경우가 많기 때문에, 유용할 것입니다.

반응형
,
반응형

현재 진행 중인 프로젝트에 로컬라이징을 적용하다가

스프링의 강력한 기능 중 하나인 ReloadedResourceBundleMessageSource에 대하여 정리가 되어, 포스트를 작성합니다.

 

[요구사항 및 해결]

1. 다국어 지원  

  - 서버가 한국에 있다고 무조건 korean으로 나와서는 안되며, 클라이언트에서 컨트롤이 가능하도록 해야 함.

 

  > 해결 : LocaleChangeInterceptor를 등록하고, 옵션 값인 ParamName을 이용하여,  

     해당 파라미터가 클라이언트로부터 도착하면 Locale을 변경하도록 설정.  

     추가로 SessionLocaleResolver라는 것을 localeResolver라는 Bean이름으로 등록해 주어야함. 

     그 뒤에 messageSource.getMessage()를 이용하면, 파라미터로 Locale을 지정해주지 않아도,  

     서버의 Locale을 참조하여, 원하는 메시지를 찾음!!

 

  * 결론 : 기본적으로 LocaleChangeInterceptor를 등록하면, localeResolver라는 이름을 가진 Bean을 찾아서 처리를 하게 되는데,

              SessionLocaleResolver를 localResolver라는 이름으로 등록하지 않으면,  

              기존에 이미 존재하던 default Resolver(AcceptHeaderLocaleResolver)에 대하여 처리가 되버림.

              AcceptHeaderLocaleResolver는 구현부를 보면 알겠지만, 특정 파라미터에 의해서 Locale을 바꾸는 것을 지원하지 않음.

              따라서, SessionLocaleResolver로 교체를 해주면, 위에 Interceptor에서 지정한 ParamName으로 Locale을 바꾸는 기능을  

              사용할 수 있게 됨.

 

2. 서버 재시작 없이 메시지 내용 변경

   - 텍스트를 하나 바꾸었다고 재시작을 한다는 것이 말이 안된다고 느끼는 분들이 좀 많음.  

     (php만 했던 사람들은 당연히 그렇게 느끼던..) 

 

   > 해결 : ReloadedResourceBundleMessageSource에 옵션으로 CacheSeconds라는게 있음.

               이게 기본으로는 -1 (영원히 캐싱, 따라서 리로드도 하지 않음)으로 되어 있기 때문에,

               1초라도 넣어주어야 해당 주기만큼 캐싱을 하고, 캐싱된 게 없으면 변경내역을 바로 불러와서 적용을 해준다.

               Tomcat을 사용하는 경우 reloadable옵션을 false로 해줘야 사용이 가능함. (true로 쓰는 경우는 거의 없지만...)

 

  ※ .properties은 모든 부분에서 사용이 가능함. 

 

 

해결은 했는데, getMessage 자체의 defaultMessage 기능이 좀 이상하게 동작하더군요.

파라미터로 온 값에 해당하는 Locale을 못찾았으면 localeResolver에 defaultLocale로 지정한 Locale에 해당하는 메시지를 찾을 줄 알았는데, 무작정 defaultMessage에 써놓은걸 뿌려버리던.... 그래서 전 try catch를 이용해서 구현을 했답니다.ㅠㅠ

이거 말고는 특별히 문제는 없는듯... 

반응형
,
반응형

Java Config를 이용하는 경우에 해당합니다. (xml 설정은 1년전에 안쓰기로 버렸기 때문에... 본 포스트에는 내용이 없습니다.)

 

4.1.0으로 넘어오면서 override되는 메소드가 두개정도 추가되었습니다만..  

필수로 구현해야 하는건 아닌거 같더군요. 

 

1. 설정 


@Configuration
@EnableCaching
public class CachingConfig implements CachingConfigurer {

    @Bean(destroyMethod="shutdown")
    public net.sf.ehcache.CacheManager ehCacheManager() {
        CacheConfiguration staticConfig = new CacheConfiguration();
        staticConfig.setName("StaticConfig");  // 캐시 이름
        staticConfig.setMemoryStoreEvictionPolicy("LRU");  // 메모리에 저장되는 방식을 결정
        staticConfig.setMaxEntriesLocalHeap(100);  // 최대로 캐싱할 수 있는 Entries의 수
        staticConfig.setTimeToIdleSeconds(3600);   
        staticConfig.setTimeToLiveSeconds(3600);
        
        net.sf.ehcache.config.Configuration config = new net.sf.ehcache.config.Configuration();
        config.addCache(staticConfig);

        return net.sf.ehcache.CacheManager.newInstance(config);
    }
    
    @Bean
    @Override
    public CacheManager cacheManager() {
        return new EhCacheCacheManager(ehCacheManager());
    }

    @Bean
    @Override
    public KeyGenerator keyGenerator() {        
        return new SimpleKeyGenerator();
    }

    @Override
    public CacheResolver cacheResolver() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public CacheErrorHandler errorHandler() {
        // TODO Auto-generated method stub
        return null;
    }

 

2. 사용     


@Cacheable(value="StaticConfig", key="#configNum")   // StaticConfig라는 캐시에 configNum을 기준으로 캐싱한다.
    public Object selectStaticConfig(int configNum) {
        // TODO
    }

    @CacheEvict(value="StaticConfig")    // StaticConfig에 캐싱되어 있던 내용을 초기화한다.
    public Object selectStaticConfigInitCache(int configNum) {
        // TODO
    } 

 

매우 간단합니다. 

반응형
,
반응형

@Before : Point cut 표현식에 해당하는 메소드를 처리하기 이전에 실행됨.

 

@After : Point cut 표현식에 해당하는 메소드를 처리한 후에 실행됨.

 

@Around : Point cut 표현식에 해당하는 메소드 주위에서 실행됨.

 

검색해서 @Around에 대한 정보를 찾다보면, 메소드 주위라는 말이 정확하게 이해가 안되더군요.

그래서 테스트를 해봄으로써 확실히 알게되었습니다.

 

@Around의 경우 다른 것들과는 달리 ProceedingJoinPoint라는 것을 argument로 가질 수 있는데요.

ProceedingJoinPoint에는 proceed() 라는 메소드가 존재합니다.

 

메소드 주위라는 말은 전과 후를 모두 처리할 수 있는 것이며,

전과 후를 구분하는 기준은 proceed() 메소드가 호출된 시점으로부터, 전 후를 의미합니다.

 

역시 글만 대충 읽어보고 적용할지 여부를 결정할 게 아니라, 테스트를 해봐야 정확한 결과를 얻을 수 있다는.. ㅠㅠ 

반응형
,
반응형

DB서버를 Scale Out 해서 사용하는 경우나, DB서버가 원격지에 분리되어 있는 경우

단일 트랜잭션만으로는 한계가 있기 때문에 (물론 사용하지 않도록 설계하는 것이 가장 좋겠지만...) 글로벌 트랜잭션을 사용하게 됩니다.

 

많이 알려진 게 아래 세가지 인데요..

2번은 3번으로 기능이 넘어가면서 Deprecated되었더군요. 

 

1. Atomikos XA TransactionManager 

 

2. Neo4j ChainedTransactionManager (Deprecated) 

 

3. Spring Data Commons ChainedTransactionManager

 

1번과 3번의 차이는 1번의 경우 2Phase Commit을 지원하는 것이고, 3번의 경우 2Phase Commit까지는 아니지만, 비슷하게 지원을 한다는 점과,  

가장 큰 것은 1번의 경우 설정이 매우 복잡하다는 점과, 3번은 설정이 매우 간편하다는 점 및 스프링과 통합이 쉽다는 점 입니다.

 

개인적인 생각으로 굳이 2Phase Commit까지 지원해야 되나.. 라는 생각과 왠지 Spring Data Commons의 경우 Spring Web MVC에서 사용하면, 안정적이지 않을까라는 생각으로 3번을 선택하게 되었습니다.

 

설정은 매우 간단합니다.

 


@Bean           // Bean 등록 시 이름이 매우 중요합니다. 이름이 다른 경우 defaultTransactionManager를 사용하게 됨.
public ChainedTransactionManager transactionManager() {
    ChainedTransactionManager transactionManager = new ChainedTransactionManager(transactionManagerGame(), transactionManagerGame_2(), transactionManagerGame_3(), transactionManagerGame_4(), ...);
    return transactionManager;
}
 
@Override        // @Transactional을 사용할 때 사용할 transactionManager를 지정해줌. (xml설정에서의 annotation-driven)
public PlatformTransactionManager annotationDrivenTransactionManager() {
    return transactionManager();
}
반응형
,
반응형

먼저 SpringFramework를 3.2버전 이상 사용하시는 분만 보시기 바랍니다.

(ControllerAdvice가 3.2버전때 만들어졌습니다.) 

 

@ControllerAdvice는 @Controller 나 @RestController(4.0부터 지원)에서 발생하는 작업을 캐치해주는 기능을 가지고 있습니다.

(특정 컨트롤러만 캐치하는 것도 가능.. 4.0부터 지원이라네요. ControllerAdvice Annotation Documentation 참조) 

저는 이것을 에러핸들링 용도로 사용해보았습니다. 

 

클래스 위에 @ControllerAdvice를 붙여주고 어떤 Exception을 캐치할 것인지, 

내부 메소드를 선언하여 메소드 상단에는 @ExceptionHandler(Exception.class) 와 같이 붙여줍니다.

최종적으로 아래와 같은 모습이 됩니다. 

 

@ControllerAdvice
@RestController 
public class ControllerAdviceHandler {
       @ExceptionHandler(NullPointerException.class)
       public Object nullPointerHandle(HttpServletRequest request, Exception e) {
            return "null"; 
       } 
        
       @ExceptionHandler(Exception.class)
       public Object exceptionHandle(HttpServletRequest request, Exception e) {
            return "exception"; 
       }

}


 

Exception의 가장 상위인 Exception을 핸들링하고, 그 하위인 NullPointerException을 핸들링하는 메소드가 따로 있는 경우,

NullPointerException은 NullPointerException 핸들링 메소드로 들어가서 처리가 됩니다. 

반응형
,
반응형

● execution 명시자 : Advice를 적용할 메서드를 명시할 때 사용

 

 기본 형식

 execution(수식어패턴? 리턴타입패턴 클래스이름패턴?이름패턴(파라미터패턴)

  ▶ 수식어패턴 : public, private 등등의 수식어를 명시, 생략 가능

  ▶ 리턴타입 : 리턴 타입을 명시

  ▶ 클래스이름, 이름패턴 : 클래스 이름 및 메서드이름을 패턴으로 명시

  ▶ 파라미터패턴 : 매칭될 파라미터에 대해 명시

  ▶ '*' : 모든 값을 표현

  ▶ '..' : 0개 이상을 의미

 

 

출처 : http://0px.kr/296

 

 

저 위에 명시된 것들은 &&나 ||를 활용해서 확장이 가능하다.

 

공통으로 처리해야될 일이 많다보니, 참고할 일이 많아서 퍼옴.. ㅠㅠ

반응형
,
반응형