STS에서 'Spring Starter Project'를 생성하고 나면 프로젝트에는 두 개의 'JRE System Library' 가 추가되는 증상이 나타난다. ㅡ_-);;



이 때는 당황하지 말고 침착하게 다음과 같이 대처한다.

  • 프로젝트의 속성창을 띄운다: 프로젝트 선택하고 Alt + Enter 를 치거나 마우스 우클릭하여 Properties 를 선택

  • Java Build Path 를 선택하고 [Libraries] 를 선택한다.
  • 하나를 선택하고 [Remove] 버튼을 눌러 두 개를 모두 삭제한다.
  • [Add Library] 를 선택하고 'JRE System Library' 를 선택한다.

  • 'Workspace default JRE' 를 선택하고 [Finish] 를 누른다.

  • [Apply] 를 누르면 정상적으로 변한 프로젝트를 확인할 수 있다.


문제

그레이들gradle 으로 구성한 멀티프로젝트를 이클립스에서 불러오게 되면 Classpath Dependency Validator 가 검증되지 않은 클래스패스 상에 의존성을 가진 프로젝트가 있다는 메시지를 던진다.

꽤 거슬린다. ㅡ_-);;

이에 대해서 인터넷 검색을 해봤다.

여기서 해답을 보면 프로젝트를 선택하고 gradle cleanEclipse eclipse 를 해서 이클립스가 그레이들 네이처를 잊어 먹도록 한 후에 수동으로 처리하도록 하는 수를 쓰고 있다.



해결책

해결방법은 간단하다.

[Preference] - 'Validation' - 'Classpath Dependency Validator' 비활성화



하면 끝난다.

경고를 무시해도, 그레이들에 의해서 프로젝트 의존성이 처리가 되기 때문에 큰 문제는 없다.

Eclipse 3.6.x 버전이 되면서 노트북에서 실행되는 이클립스가 얼어버리는 되는 상황들이 빈번하게 발생하기 시작했다. 코딩을 하다가 탭을 바꾸거나 자동완성 기능을 사용하려고 하면 화면이 멈추는 증상에 너무 짜증이 나서 어떻게 할까 고민하던 차다.

지금 노트북에는 외장그래픽으로 ATI Radeon 이 포함되어 있다.

우분투랑 ATI 라데온 조합이 안좋은 건 이 노트북을 사용하면서 실감했다. 우분투와 궁합이 괜찮은 건 엔비디아nVidia 와 인텔intel 이다.

이클립스를 실행할 때마다 너무 짜증이나서 운영체제를 다시 설치할까 했는데, 문득 이에 대한 인터넷 검색을 해보자 하는 생각이 들었다(이렇게 쓴지 한달이 좀 넘은 것 같은데...).

인터넷 검색으로는 ubuntu 14.04 eclipse slow 으로 검색을 시작했다. 그랬더니 나오는 첫번째 결과가 똬악!

을 통해서 간단하게 방법을 찾았다. 해결책은 크게 두 가지 방법이 있다.

  • eclipse.sh 실행 스크립트 작성

    export SWT_GTK3=0
    export UBUNTU_MENUPROXY=0
    ./eclipse
  • eclipse.desktop 애플리케이션 등록정보 수정

    [Desktop Entry]
    Version=4.4
    Name=Eclipse
    Comment=Eclipse IDE
    Exec=env UBUNTU_MENUPROXY=0 SWT_GTK3=0 =/home/honeymon/development/eclipse/eclipse
    Icon=/home/honeymon/development/eclipse/icon.xpm
    Terminal=false
    Type=Application
    Categories=Utility;Application

위의 두 가지 방법 중에 후자의 것을 사용했다.


문제가 생긴 이유는, GTK3 의 SWT 가 최적화되어 있지 않기 때문에 나타나는 버그로 보인다.

Evolve your Database Schema easily and reliably across all your instances

데이터베이스 스키마의 변화를 손쉽게 관리

하는 기능을 가진 기술



1. FlywayDB 소개

1.1. 왜 DB Migration 을 사용하는가?

각기 다른 데이터베이스에서 동일한 DB스키마를 유지할 수 있는 기능이 필요하다. Shiny DB migration

  • 코드쪽에서는 꽤 괜찮은 해결책들이 있다.

    • 버전관리 도구는 보편적으로 사용되고 있다
    • 재현가능한 빌드와 지속적인 통합을 실시
    • 괜찮게 정의된 릴리스 및 배포 프로세스를 가지고 있다.
  • 그런데 데이터베이스는??

    • 많은 프로젝트가 수동으로 SQL을 실행하는 방식 수행
    • 이 과정에서 많은 문제가 발생함
      • 각 데이터베이스마다 DB 스키마의 구조가 달라질 수 있는 문제가 발생
    • 관련된 많은 질문들
      • 이 시스템의 데이터베이스는 어떤 상태입니까?
      • 이 스크립트는 이미 적용 했습니까?
      • 제품에 적용한 긴급수정사항은 테스트 데이터베이스에 적용했습니까?
      • 어떻게 새로운 데이터베이스 인스턴스를 설정합니까?

        보다 많은 질문들에 대해서는: 모르겠다!!

  • 데이터베이스 마이그레이션으로 이 무제들을 해결할 수 있다!

    • 처음부터 데이터베이스를 재작성하고
    • 데이터베이스가 어떤 상태인지 확인가능하고
    • 새로운 데이터베이스에 현재 적용되어있는 방식으로 마이그레이션 가능

2. FlywayDB 동작방식 설명

Empty Database

마이그레이션 버전 1, 버전 2가 있는 상황에서 flyway는 SCHEMA_VERSION 라는 이름의 비어있는 단독 테이블을 생성한다.

Emtpy schema version table

테이블이 생성되면 flyway는 즉시 파일시스템 혹은 애플리케이션의 마이그레이션을 위해 지정된 클래스패스에서 SQL 혹은 JAVA 파일을 탐색한다. 탐색된 파일들은 버전에 따라서 정렬하여 실행한다. execute migration

마이그레이션 버전1, 버전2가 각각 실행되면 SCHEMA_VERSION 테이블에는 다음과 같이 마이그레이션 이력이 저장된다.

Table schema_version

애플리케이션이 실행될때마다 flyway 설정에 따라서 파일과 SCHEMA_VERSION의 변동사항을 확인하나. 새로운 버전의 파일이 추가되면 실행하고 그 변경이력을SCHEMA_VERSION에 추가한다.

Pending Migration

마이그레이션 버전 2.1 후 변동사항

SCHEMA_VERSION의 변동사항

참 쉽죠??


3. 프로젝트 적용

3.1. SpringBoot 설정

스프링부트에서는 데이터베이스 마이그레이션을 지원한다. 그 중에서 Flyway를 채택했다. 이전 프로젝트에서는 Carbon5(c5-db-migration) 라고 하는 마이그레이션 도구를 사용했는데...

  • Maven만 지원했음
  • 지원이 사라짐...

그러다가 발견한 것이 Flyway. 손쉽게 사용가능함.

3.1.1. 의존성 추가

Maven

  • Flyway: maven
    <project ...>
    ...
    <build>
      <plugins>
        <plugin>
          <groupId>org.flywaydb</groupId>
          <artifactId>flyway-maven-plugin</artifactId>
          <version>3.2.1</version>
          <configuration>
            <url>jdbc:h2:file:target/foobar</url>
            <user>sa</user>
          </configuration>
          <dependencies>
            <dependency>
              <groupId>com.h2database</groupId>
              <artifactId>h2</artifactId>
              <version>1.3.170</version>
            </dependency>
          </dependencies>
        </plugin>
      </plugins>
    </build>
    </project>

Gradle

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.h2database:h2:1.3.170'
        classpath 'org.flywaydb:flyway-gradle-plugin:3.2.1'
    }
}
 
apply plugin: 'org.flywaydb.flyway'
apply plugin: 'java'
 
flyway {
    url = 'jdbc:h2:file:target/foobar'
    user = 'sa'
}

SpringBoot

dependencies {
  /**
   * http://flywaydb.org/
   * Database Migration tool
   */
  compile "org.flywaydb:flyway-core"
}

3.1.2. application.yml(or properties) 설정

DB를 초기화하는 다양한 방법들이 있다.

  • JPA 에 의한 초기화
  • Hibernate 에 의한 초기화
  • JDBC에 의한 초기화
  • Batch를 이용한 초기화

그보다 높은 차원에서 손쉽게할 수 있는 것들이 있는데, 그중 하나가 바로 Flyway

flyway:
  enabled: true
  check-location: true # 마이그레이션 스크립트 파일이 존재하는지 여부를 확인
  locations: classpath:db/migration # 마이그레이션 스크립트 위치
  baseline-version: LATEST # 마이그레이션을 시작할 번호
  sql-migration-prefix: V
  sql-migration-suffix: .sql
  url: jdbc:h2:file:~/.database/flywaydb;DB_CLOSE_DELAY=-1;AUTO_SERVER=TRUE;DB_CLOSE_ON_EXIT=FALSE;  # Flyway 소유의 데이터소스를 생성하려고 하는 경우 사용
  user: sa # Flyway 소유의 데이터소스를 생성하려고 하는 경우 사용
  password: # Flyway 소유의 데이터소스를 생성하려고 하는 경우 사용

Flyway에 지정할 수 있는 속성은 다음과 같음

# FLYWAY (FlywayProperties)
flyway.*= # Any public property available on the auto-configured `Flyway` object
flyway.check-location=false # check that migration scripts location exists
flyway.locations=classpath:db/migration # locations of migrations scripts
flyway.schemas= # schemas to update
flyway.init-version= 1 # version to start migration
flyway.init-sqls= # SQL statements to execute to initialize a connection immediately after obtaining it
flyway.sql-migration-prefix=V
flyway.sql-migration-suffix=.sql
flyway.enabled=true
flyway.url= # JDBC url if you want Flyway to create its own DataSource
flyway.user= # JDBC username if you want Flyway to create its own DataSource
flyway.password= # JDBC password if you want Flyway to create its own DataSource

3.1.2. 디렉토리 설명

3.2. 스키마 관리방법 설명

  • 프로젝트의 소스코드와 함께 관리한다.
  • 로컬, 테스트와 출시 버전을 분리한다.
    • 로컬, 개발은 update로도 충분...
    • 테스트, 운영 등 서버는 validate 모드로 관리
  • 프로젝트의 버전관리시스템에 기능을 활용하여 버전관리가 가능

3.2.1. DB 스키마 추출방법

어디까지나 수동.... 처음에는 수동처리가 필요함...

  1. 로컬 테스트에서 Hibernate에서 DDL 쿼리를 생성하도록 정의 생성하도록 설정

    jpa:
    hibernate:
     ddl-auto: create-drop
  2. 테스트를 실행

    사용할려는 DB에 따라 생성하는 SQL 스크립트가 달라질 수 있는데, 이는H2Database 에서 제공하는 기능을 활용하여 MySQL, Oracle 등으로 변경하면 하이버네이트의 방언Dialect 에 의해 적절한 쿼리가 생성됨

  3. 쿼리를 복사하여 손질


  4. 처음 쿼리는 V1__init_db_schema.sql 로 생성

3.2.2. 변경이력 관리

프로젝트 내에 포함되어 있으니 모든 것을 형상관리시스템에 맡긴다.

3.3. 활용방안

  • 제품 출시 후 DB 스키마를 초기화하는 것이 아니라, 출시 이후 변경된 사항들에 대한 형상을 관리할 수 있다.
  • 위에서 설명한 동작방식을 상기하기 바란다.


얼마 전 Eclipse Mars가 출시하고, 스프링에서는 이클립스 마르스를 얹은 STS.3.7.0 버전을 내놓았다.

나는 지금까지 개발할 때 Lombok을 많이 사용해왔다. 클래스 내 필드에 대한 Getter/Setter 생성이나 toString(), equals(), hashCode() 메서드를 오버라이드 하기 위해 클래스에 코드가 덕지덕지 붙는 것을 어노테이션들(@ToString,@EqualsAndHashCode, @Getter, @Setter, @Data 등)로 대신할 수 있기 때문이다. 자바로 개발할 때 겪게되는 반복적이고 가독성에 크게 도움되지 않는 녀석들을 컴파일과정에서 어노테이션을 기반으로 하여 자동생성해주기에 코드가 그나마 많이 간결해진다.

그런데!! 이클립스 루나까지는 크게 문제가 없던 STS(3.6.0)에서 3.7.0으로 버전업을 하고 난 이후에 몇몇 결함이 발생한다. 예를 들어,

  • 현재 열고 있는 클래스에 대한 유닛테스트를 위해 클래스를 만들려고 하면 비어있는 파일만 생성하기
  • Getter/Setter 생성시 원인모를 예외를 발생시키기
  • 인스턴스 메서드에서 던지는 throws Exception 을 메서드에 반영하지 못함
  • 구현하고 있는 인터페이스에 메서드를 생성하지 못함

등... 개발하는 과정에서 자주하는 일들을 할 수가 없는 것은, 가뜩이나 좋지 않은, 생산성을 뚜욱하고 떨어뜨리는 슬픈 상황을 만들었다. 그래도 투덜거리면서 쓰다가,

투걸거리지만 말고, 버그 리포팅을 해서 개선할 수 있도록 해주세요.

간단하게 Getter/Setter를 만드는 작업을 진행해보면 다음과 같은 증상이 나온다.





  • 에러코드
eclipse.buildId=3.7.0.201506290652-RELEASE-e45
java.version=1.8.0_20
java.vendor=Oracle Corporation
BootLoader constants: OS=linux, ARCH=x86_64, WS=gtk, NL=ko_KR
Framework arguments:  -client -product org.springsource.sts.ide
Command-line arguments:  -os linux -ws gtk -arch x86_64 -client -product org.springsource.sts.ide -console
 
java.lang.reflect.InvocationTargetException
    at org.eclipse.jface.operation.ModalContext.runInCurrentThread(ModalContext.java:476)
    at org.eclipse.jface.operation.ModalContext.run(ModalContext.java:371)
    at org.eclipse.ui.internal.WorkbenchWindow$14.run(WorkbenchWindow.java:2156)
    at org.eclipse.swt.custom.BusyIndicator.showWhile(BusyIndicator.java:70)
    at org.eclipse.ui.internal.WorkbenchWindow.run(WorkbenchWindow.java:2152)
    at org.eclipse.ui.internal.progress.ProgressManager$RunnableWithStatus.run(ProgressManager.java:1394)
    at org.eclipse.swt.custom.BusyIndicator.showWhile(BusyIndicator.java:70)
    at org.eclipse.ui.internal.progress.ProgressManager$5.run(ProgressManager.java:1228)
    at org.eclipse.swt.widgets.Synchronizer.syncExec(Synchronizer.java:186)
    at org.eclipse.ui.internal.UISynchronizer.syncExec(UISynchronizer.java:145)
    at org.eclipse.swt.widgets.Display.syncExec(Display.java:4633)
    at org.eclipse.ui.internal.progress.ProgressManager.runInUI(ProgressManager.java:1225)
    at org.eclipse.jdt.ui.actions.AddGetterSetterAction.run(AddGetterSetterAction.java:618)
    at org.eclipse.jdt.ui.actions.AddGetterSetterAction.generate(AddGetterSetterAction.java:549)
    at org.eclipse.jdt.ui.actions.AddGetterSetterAction.run(AddGetterSetterAction.java:340)
    at org.eclipse.jdt.ui.actions.AddGetterSetterAction.run(AddGetterSetterAction.java:584)
    at org.eclipse.jdt.ui.actions.SelectionDispatchAction.dispatchRun(SelectionDispatchAction.java:279)
    at org.eclipse.jdt.ui.actions.SelectionDispatchAction.run(SelectionDispatchAction.java:251)
    at org.eclipse.jface.action.Action.runWithEvent(Action.java:473)
    at org.eclipse.jface.action.ActionContributionItem.handleWidgetSelection(ActionContributionItem.java:595)
    at org.eclipse.jface.action.ActionContributionItem.access$2(ActionContributionItem.java:511)
    at org.eclipse.jface.action.ActionContributionItem$5.handleEvent(ActionContributionItem.java:420)
    at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:84)
    at org.eclipse.swt.widgets.Display.sendEvent(Display.java:4481)
    at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1327)
    at org.eclipse.swt.widgets.Display.runDeferredEvents(Display.java:3819)
    at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:3430)
    at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine$4.run(PartRenderingEngine.java:1127)
    at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:337)
    at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine.run(PartRenderingEngine.java:1018)
    at org.eclipse.e4.ui.internal.workbench.E4Workbench.createAndRunUI(E4Workbench.java:156)
    at org.eclipse.ui.internal.Workbench$5.run(Workbench.java:654)
    at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:337)
    at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:598)
    at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:150)
    at org.eclipse.ui.internal.ide.application.IDEApplication.start(IDEApplication.java:139)
    at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:196)
    at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:134)
    at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:104)
    at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:380)
    at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:235)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:669)
    at org.eclipse.equinox.launcher.Main.basicRun(Main.java:608)
    at org.eclipse.equinox.launcher.Main.run(Main.java:1515)
    at org.eclipse.equinox.launcher.Main.main(Main.java:1488)
Caused by: java.lang.IndexOutOfBoundsException: Index: 5, Size: 5
    at java.util.ArrayList.rangeCheck(ArrayList.java:653)
    at java.util.ArrayList.get(ArrayList.java:429)
    at org.eclipse.jdt.internal.formatter.TokenManager.get(TokenManager.java:68)
    at org.eclipse.jdt.internal.formatter.TokenManager.findIndex(TokenManager.java:161)
    at org.eclipse.jdt.internal.formatter.TokenManager.firstIndexIn(TokenManager.java:188)
    at org.eclipse.jdt.internal.formatter.TokenManager.firstTokenIn(TokenManager.java:194)
    at org.eclipse.jdt.internal.formatter.SpacePreparator.visit(SpacePreparator.java:196)
    at org.eclipse.jdt.core.dom.MethodDeclaration.accept0(MethodDeclaration.java:611)
    at org.eclipse.jdt.core.dom.ASTNode.accept(ASTNode.java:2711)
    at org.eclipse.jdt.core.dom.ASTNode.acceptChildren(ASTNode.java:2782)
    at org.eclipse.jdt.core.dom.TypeDeclaration.accept0(TypeDeclaration.java:470)
    at org.eclipse.jdt.core.dom.ASTNode.accept(ASTNode.java:2711)
    at org.eclipse.jdt.internal.formatter.DefaultCodeFormatter.prepareSpaces(DefaultCodeFormatter.java:350)
    at org.eclipse.jdt.internal.formatter.DefaultCodeFormatter.prepareFormattedCode(DefaultCodeFormatter.java:193)
    at org.eclipse.jdt.internal.formatter.DefaultCodeFormatter.format(DefaultCodeFormatter.java:155)
    at org.eclipse.jdt.internal.formatter.DefaultCodeFormatter.format(DefaultCodeFormatter.java:139)
    at org.eclipse.jdt.internal.core.dom.rewrite.ASTRewriteFormatter.formatString(ASTRewriteFormatter.java:246)
    at org.eclipse.jdt.internal.core.dom.rewrite.ASTRewriteFormatter.formatNode(ASTRewriteFormatter.java:376)
    at org.eclipse.jdt.internal.core.dom.rewrite.ASTRewriteFormatter.getFormattedResult(ASTRewriteFormatter.java:187)
    at org.eclipse.jdt.internal.core.dom.rewrite.ASTRewriteAnalyzer.doTextInsert(ASTRewriteAnalyzer.java:1357)
    at org.eclipse.jdt.internal.core.dom.rewrite.ASTRewriteAnalyzer$ListRewriter.rewriteList(ASTRewriteAnalyzer.java:647)
    at org.eclipse.jdt.internal.core.dom.rewrite.ASTRewriteAnalyzer$ListRewriter.rewriteList(ASTRewriteAnalyzer.java:802)
    at org.eclipse.jdt.internal.core.dom.rewrite.ASTRewriteAnalyzer.rewriteParagraphList(ASTRewriteAnalyzer.java:1175)
    at org.eclipse.jdt.internal.core.dom.rewrite.ASTRewriteAnalyzer.visit(ASTRewriteAnalyzer.java:1811)
    at org.eclipse.jdt.core.dom.TypeDeclaration.accept0(TypeDeclaration.java:453)
    at org.eclipse.jdt.core.dom.ASTNode.accept(ASTNode.java:2711)
    at org.eclipse.jdt.core.dom.rewrite.ASTRewrite.internalRewriteAST(ASTRewrite.java:302)
    at org.eclipse.jdt.core.dom.rewrite.ASTRewrite.rewriteAST(ASTRewrite.java:291)
    at org.eclipse.jdt.internal.corext.codemanipulation.AddGetterSetterOperation.run(AddGetterSetterOperation.java:351)
    at org.eclipse.jdt.internal.core.BatchOperation.executeOperation(BatchOperation.java:39)
    at org.eclipse.jdt.internal.core.JavaModelOperation.run(JavaModelOperation.java:729)
    at org.eclipse.core.internal.resources.Workspace.run(Workspace.java:2241)
    at org.eclipse.jdt.core.JavaCore.run(JavaCore.java:5409)
    at org.eclipse.jdt.internal.ui.actions.WorkbenchRunnableAdapter.run(WorkbenchRunnableAdapter.java:106)
    at org.eclipse.jface.operation.ModalContext.runInCurrentThread(ModalContext.java:463)
    ... 48 more

라고 올챙이 프로젝트(https://github.com/hangum/TadpoleForDBTools)를 지휘하고 계시는 조현종님이 말씀하시기에 '그래, 버그를 수정할 수 있도록 돕자'하는 마음에 이클립스의 'Error View'를 열고 이클립스에서 생긴 예외상황을 찾아봤다. 그리고 이 것을 SNS에 올리니까

그거 롬복 때문에 생긴 문제에요. 롬복 최신버전 받으면 정상적으로 동작해요.

라고 지인(http://sbcoba.tistory.com/)이 알려주었다. 이 때 찾아드는 허무감이란... 요 근래에 개발환경에 큰 변화가 찾아오기는 했다.

  • JDK 7 -> JDK 8
  • Eclipse Luna -> Eclipse Mars

이런 변화를 간과하고 이클립스만 탓한 내가 부끄러워지는구나.... 사용하고 있던 롬복은 v1.14.8 버전이다. 현재(2015/08/27) project lombok에서 다운로드 가능한 버전은1.16.6(https://projectlombok.org/changelog.html)이다. 8월 16일에 반영된 내용을 살펴보니

이다. 이클립스 마르스와 관련해서 버그가 있었던 것이었다. 최신버전 받으면 해결된다. 혹은... 롬복을 사용하지 않으면 된다.


+ Recent posts