[springboot] 스프링 부트 1.5.14.RELEASE 와 2.0.3.RELEASE 출시

지난 2018/06/14 스프링 부트 1.5 와 2.0 새로운 버전이 출시되었다. 기존에 있던 결함을 수정하고 의존성 라이브러리들을 업데이트 하는 수준이었다.

그런데 의도치 않게 내게 큰 문제가 발생을 하니…​ 바로 Lombok과 관련된 문제였다.

두 버전 모두 lombok 1.16.22 버전을 사용한다.

1.16.20 은 이상이 없었지만 1.16.22 버전에서는 다음과 같이 사용하면 컴파일 에러가 발생한다.

@Data
@NoArgsConstructor
public class Transfer {
    private String name;
    private Integer value;

    public Transfer(String name, Integer value) {
        this.name = name;
        this.value = value;
    }
}
Error:(9, 1) java: constructor Transfer() is already defined in class io.honeymon.boot.springboot.training.Transfer

이 문제를 해결하는 방법은 간단하다.

위 내용을 살펴보고, 세부적인 항목들을 명시적으로 작성하자.

@Getter
@Setter
@NoArgsConstructor
@ToString(onlyExplicitlyIncluded = true)  // (1)
@EqualsAndHashCode(onlyExplicitlyIncluded = true) // (2)
public class Transfer {

    @ToString.Include // (1)
    @EqualsAndHashCode.Include // (2)
    private String name;

    @ToString.Include
    @EqualsAndHashCode.Include
    private Integer value;

    @Builder
    public Transfer(String name, Integer value) {
        this.name = name;
        this.value = value;
    }
}

<1>, <2> lombok 1.16.22 버전부터 추가된 @Include@Exclude 를 이용해서 필드에서 toString()equals(), hashCode() 에서 사용할 필드를 지정할 수 있다. (onlyExplicitlyIncluded = true)를 선언해줘야 실제로 적용된다.

Note

명시적으로 필드선언을 했을 때 타입에 선언된 부분에 (onlyExplicitlyIncluded = true)를 선언해야 적용된다.

/**
 * Only include fields and methods explicitly marked with {@code @ToString.Include}.
 * Normally, all (non-static) fields are included by default.
 */
boolean onlyExplicitlyIncluded() default false;
Warning

그런데 이번에 배포된 1.16.22 에서는 (onlyExplicitlyIncluded = true) 부분에서 컴파일 에러가 발생한다. 이를 해결하기 위해서는 롬복 1.18.0 으로 변경해야 한다.

Error:(12, 45) java: Can't translate a class java.lang.Boolean to the expected class java.lang.Boolean

이전에는

@Getter
@Setter
@NoArgsConstructor
@ToString(of={"name", "value"})
@EqualsAndHashCode(of={"name", "value"})
public class Transfer {

    private String name;

    private Integer value;

    @Builder
    public Transfer(String name, Integer value) {
        this.name = name;
        this.value = value;
    }
}

위와 같이 of 속성에 문자배열로 등록해야해서 복사하여 붙여넣거나 타이핑을 하다가 오타가 발생할 가능성이 있었던 것을 피할 수 있게 되었다.

@Getter
@Setter
@NoArgsConstructor
@ToString(onlyExplicitlyIncluded = true)
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Baggage {
    @EqualsAndHashCode.Include
    @ToString.Include
    private String name;    // 수화물명

    @EqualsAndHashCode.Include
    @ToString.Include
    private Integer value;  // 가치

    @EqualsAndHashCode.Include
    @ToString.Include
    private String departure;   // 출발지

    @EqualsAndHashCode.Include
    @ToString.Include
    private String arrival;     // 도착지

    private String description;

    @Builder
    public Baggage(String name, Integer value, String departure, String arrival, String description) {
        this.name = name;
        this.value = value;
        this.departure = departure;
        this.arrival = arrival;
        this.description = description;
    }
}

위와 같이 작성하면 된다.

정리

  • 스프링 부트 1.15.14와 2.0.3 출시

  • 롬복(Lombok)

    • 위 버전에 추가된 Lombok 1.16.22 버전 명시

    • @Data@NoArgsConstructor 는 함께 사용할 수 없다.

      • @Data@NoArgsConstructor가 기본 생성자(Default Consturctor)를 생성하는 부분에서 컴파일러 에러가 발생하는 것으로 추측할 수 있다.

    • @ToString@EqualsAndHashCode를 필드에 선언할 수 있다. 이 기능을 적용하려면 타입영역에서 @ToString(onlyExplicitlyIncluded = true) 으로 선언해야 한다.

    • 1.16.22 에서 컴파일 에러가 있어서 1.18.0 이 나옴

    • 1.18.0 버전을 사용하라.

또 삽질을 하러 갑시다.

Flyway는 개발영역에서만 사용

이용전략

  • 개발(로컬)에는 V1__, V2__, V3__ 으로 자유롭게 업데이트 한다.

  • commit & push 전(공개 전): 하나의 파일로 통합하여 정리한 후 정상동작을 확인하고 푸시한다.

  • 스테이지, 운영 단계에 스키마 변경에는 Flyway를 사용하지 않는다.

    • 스키마 변경 전에는 DB 상태 모니터링 하면서 장인정신으로 한땀한땀 반영한다.

    • DB 락 여부 확인, 속도 저하 등을 확인하며 진행

    • 운영데이터가 많이 누적된 경우에 처리속도가 느려 락이 걸리는 등의 상황이 발생할 수 있다.

추가사항

  • H2 인-메모리 디비는 프로토타이핑 용으로 사용한다.

  • H2에서 사용하는 쿼리가 실제 운영단계에 반영되었을 때 문제발생 가능성이 높다.

  • cleanOnValidationError: 검증 중에 에러가 발생하면 자동으로 클린 처리를 함. ← 위험!!

cleanOnValidationError

Whether to automatically call clean or not when a validation error occurs.

This is exclusively intended as a convenience for development. Even tough we strongly recommend not to change migration scripts once they have been checked into SCM and run, this provides a way of dealing with this case in a smooth manner. The database will be wiped clean automatically, ensuring that the next migration will bring you back to the state checked into SCM.

Warning ! Do not enable in production !


스프링 사이트(https://spring.io/)가 리모델링을 거쳤다.

전체적으로 깔끔해지고 카테고리가 정리되었다.

그와 함께 스프링 부트의 위상이 크게 격상되었다는 것도 보인다.

스프링 부트로 만들고

스프링 클라우드로 결합시키고

스프링 클라우드 데이터 플로우로 뭐든지 연결한다!? (아직 스프링 클라우드 데이터 플로우 쪽에는 가보지도 못했는데...)

말그대로 스프링 플랫폼을 위한 기반으로서 확고한 자리를 잡았다. 쉽게 개발하고 배포 및 운영이 용이하기 때문에 앞으로도 꾸준하게 사용자가 늘어날 것이다.

그런 의미에서!! 다음달 중에 나올 제 책을 꼭~ 봐주세요. ㅎㅎ


[springboot] The value of a manifest attribute must not be null (Key=Start-Class)

그레이들을 기반으로 스프링 부트 프로젝트 스크립트를 하나하나 작성하다보니 장애하나를 접했다.

$ ./gradlew clean build
FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':c-app:bootJar'.
> The value of a manifest attribute must not be null (Key=Start-Class). // (1)

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 1s
10 actionable tasks: 10 executed
  1. Start-Class

눈치가 있는 분이라면 (Key=Start-Class) 항목이 눈에 띌 것이다. 스프링 부트 그레이들 플러그인에서 bootJar 태스크를 실행하는 과정에서 시작 클래스를 찾지 못했다. 이 문제가 발생할 당시에 메인클래스를 작성하지 않았다(혹은 public static void main(String[] args) 메서드가 작성되지 않았을 때 발생).

public class Application {
    public static void main(String[] args) {}
}

다음과 같은 형태로 간단한 메인클래스를 작성하면 빌드는 정상적으로 진행된다.


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()); 실행결과

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

+ Recent posts