20180425 [spring] StopWatch 기능 테스트

스톱워치 기능은 자주 사용된다. 보통은 다음과 같은 형태로 구현한다. 시스템(System) 클래스에서 밀리세컨드 현재 밀리세컨드값을 가져와 그 차이를 구하는 방식을 사용한다.

@Test
public void testSimpleTimer() {
    long startTime = System.currentTimeMillis();
    int data = 0;
    for(int i = 0; i < 1_000_000; i++) {
        data += 1;
    }
    long elapsedTime = System.currentTimeMillis() - startTime;
    System.out.println(elapsedTime);
}

간결하게 사용하기는 나쁘지 않다.

다음 예제는 스프링 프레임워크에서 제공하는 유틸 중 하나인 org.springframework.util.StopWatch에 대한 간단한 테스트다.

public class StopWatchTest {

    private StopWatch stopWatch;

    @Before
    public void setUp() {
        stopWatch = new StopWatch("honeymon");
    }

    /**
     * Long 타입과 BigDecimal 타입의 덧셈 소요시간 비교:
     */
    @Test
    public void testAddLongAndBigDecimal() {
        BigDecimal bigDecimal = BigDecimal.valueOf(0, 0);
        Long longType = 0L;

        stopWatch.start("Long type");
        for(int i = 0; i < 1_000_000; i++) {
            longType += 1L;
        }
        stopWatch.stop();

        stopWatch.start("BigDecimal type");
        for(int i = 0; i < 1_000_000; i++) {
            bigDecimal = bigDecimal.add(BigDecimal.ONE);
        }
        stopWatch.stop();
        System.out.println(stopWatch.shortSummary());
        System.out.println(stopWatch.getTotalTimeMillis());
        System.out.println(stopWatch.prettyPrint());
    }
}

위의 testAddLongAndBigDecimal 테스트를 실행한 결과는 다음과 같다.

StopWatch 'honeymon': running time (millis) = 42  // (1)
42                                                // (2)
StopWatch 'honeymon': running time (millis) = 42  // (3)
-----------------------------------------
ms     %     Task name
-----------------------------------------
00017  040%  Long type
00025  060%  BigDecimal type


Process finished with exit code 0
  1. System.out.println(stopWatch.shortSummary()); 실행결과

  2. System.out.println(stopWatch.getTotalTimeMillis()); 실행결과

  3. System.out.println(stopWatch.prettyPrint()); 실행결과

몇 가지 비교군의 소요시간을 확인해야하는 상황이 있다면 이용해봄직하다.

Spring은 사용하려는 프로파일을 정의하여 상황에 따라 컴포넌트에 대한 등록 및 제외를 결정할 수 있다.

@Profile({"dev", "!kr"})

이라고 프로파일을 정의하면 조건식은 dev or !kr 이 되어 dev 혹은 kr 에 대해서 선언되어 있지 않으면 반드시 실행되는 상황이 발생한다.

이런 상황을 피할 수 있는 방법으로 Spring 4.0에서 추가된 @@Conditional 을 사용하는 방법이 있다. 이와 관련한 질문은 How to conditionally declare Bean when multiple profiles are not active? 를 살펴보면 고민하고 있는 유사한 내용과 답변을 볼 수 있다.

간단한 해결책은 Condition을 구현하는 것이다. 다음과 같이 간단하게 dev and !kr을 만족하는 조건식을 작성해보자.

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class PrdAndIgnoreKrProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().acceptsProfiles("prd") && !context.getEnvironment().acceptsProfiles("kr");
}
}

위의 클래스를 사용하여

@Profile({"dev", "!kr"})
//을 대신하여
@Conditional(PrdAndIgnoreKrProfileCondition.class)
// 으로 정의하면 dev and !kr 조건식이 적용가능해진다.



Thymeleaf 에서 스프링 환경변수 사용하기

Thymeleaf 에서 스프링 프로퍼티 값 사용하는 방법

  • @ 뒤에 빈(Bean) 이름을 사용하면 그 빈에 접근할 수 있다.

타임리프에서 스프링의 property 파일(application.property 혹은 application.yml 등)에 기술되어 있는 변수를 이용하려는 경우

${@environment.getProperty('property.key')}

프로파일 환경에 따라 표시를 하려면

<div th : if = $ { @environment .acceptsProfiles ( 'production' )}>
This is the production profile
</ div>
or
<div th : if = "$ {# arrays.contains (@ environment.getActiveProfiles () 'production')} " >
     This is the production profile
</ div>

시스템 환경변수를 이용하는 경우

$ { @systemProperties [ 'property.key' ]}


바보, 삽질을 하다.

public class WBAccessDeniedHandler implements AccessDeniedHandler {
 
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOExceptionServletException {
        String redirectUrl = "/";
        response.sendRedirect(redirectUrl);
    }
}

redirectUrl 는 반드시 / 으로 시작해야 한다. 안그러면…​ 접근한 requestUrl 에 끊없이 redirectUrl 붙다가 오류가 난다.


발생문제

org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags: [entity.list_collection_a, entity.list_collection_b]
@ElementCollection(targetClass = EnumType.class, fetch = FetchType.EAGER)
@Enumerated(EnumType.STRING)

해당필드는 enum 타입의 목록을 가지는 필드였고, 프로젝트의 의존성 라이브러리 버전들을 업그레이드 하면서org.hibernate.loader.MultipleBagFetchException 이 발생했다.

  • 업그레이드

    • org.hibernate:hibernate-entitymanager:5.1.0.Final →org.hibernate:hibernate-entitymanager:5.2.4.Final

해결방법

이 문제를 해결하는 방법은 enum 타입 컬렉션 필드에 정의를 다음과 같이 변경했다.

@ElementCollection(targetClass = EnumType.class)
@Enumerated(EnumType.STRING)
@LazyCollection(LazyCollectionOption.FALSE)

@ElementCollection 는 기본적으로 LAZY 값을 가진다. 그러나 한단에 선언된@LazyCollection(LazyCollectionOption.FALSE) 을 통해서 EAGER 로 처리된다.@LazyCollection 는 컬렉션 타입에 대한 LAZY 여부를 결정하는 기능을 한다.


+ Recent posts