2017년 7월 18일 화요일

Spring Boot + MyBatis 프로젝트 설정하기

 Spring Boot를 이용하면 MyBatis도 매우 간단하게 연동해서 사용할 수 있다. 어떤 라이브러리(프레임워크)든지 프로젝트에 추가하려고 할 때 내가 가장 먼저 관심을 갖는 것은 가능한 최소한의 설정이 무엇인가 이다. 일단은 가장 최소한의 설정으로 기본적인 기능을 그대로 이용해보고 필요한 추가 설정이 생기면 그때그때 조금씩 수정해가는 것이 가장 깔끔한 상태를 유지하는 길이라고 생각하기 때문이다.

 Spring Boot와 MyBatis를 함께 사용하는 것은 그러한 측면으로는 정말 최소한의 설정만으로 큰 만족을 얻을 수 있다.

 일단 IntelliJ IDEA CE버전으로 기본적인 Spring Boot 프로젝트를 생성하도록 하자. (생성방법은 이전 글을 참고하기 바란다.) 최초 생성 직후의 build.gradle 파일의  dependency는 'spring-boot-starter'가 들어가 있겠지만 MyBatis를 이용하려면 아래와 같이 'mybatis-spring-boot-starter'로 교체해준다.
dependencies {
    compile('org.mybatis.spring.boot:mybatis-spring-boot-starter:1.3.0')
    compile('org.mariadb.jdbc:mariadb-java-client:2.0.3')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}
 사실 이전 글의 생성방법에 따라 프로젝트를 생성할 때 Spring Initializr화면을 Full Version으로 전환한 후 MyBatis 체크박스에 체크를 하면 'spring-boot-starter' 대신 'mybatis-spring-boot-starter'가 dependency에 들어간다. 하지만 이렇게 기본 생성된 파일을 직접 수정해줘도 똑같다.

 추가로 JDBC 드라이버를 dependency에 추가해주어야 한다. 위의 예에서는 MariaDB의 JDBC 드라이버를 넣어주었지만 각자 자신의 DB에 맞는 드라이버를 설정해주도록 하자.

 다음으로 'src/main/resources/'에 있는 application.properties 파일에 data source를 설정해주도록 하자. (파일이 없으면 만들어준다.) data source가 설정이 되어있지 않으면 예외가 발생하면서 정상적으로 실행되지 않으므로 반드시 해주어야 한다.
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.0.33:3306/Test
spring.datasource.username=testuser
spring.datasource.password=testpass
 각 항목에 대한 설명은 굳이 하지 않아도 JDBC 유 경험자라면 다들 알만한 것들이다. 자신의 DB 생성 상황에 맞게 위 값들을 맞춰서 넣어주도록 하자. (당연히 'Test' DB와 'testuser' 사용자 계정 같은 것들은 DB에 준비가 되어 있어야 한다.)

 일단 설정 파일 수정 사항은 이게 끝이다. 이렇게 data source 설정만으로 MyBatis를 사용할 수 있으니 최소한의 설정임에 틀림이 없는 것 같다.

 이제 Java 코드에 손 댈 차례다. 먼저 Mapper로 사용할 Interface를 만들어 준다.
package com.company.dbtest.mapper;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface TestMapper {
    String getTime();
}
 mapper들을 모아 놓을 package (com.company.dbtest.mapper)에 TestMapper 인터페이스를 위와 같이 선언해서 넣어놓는다.

 다음으로 프로젝트 생성 시 같이 생성된 @SpringBootApplication 클래스를 열어서 아래와 같이 수정해준다.
package com.company.dbtest;

import com.company.dbtest.mapper.TestMapper;
...

@SpringBootApplication
@MapperScan("com.company.dbtest.mapper")
public class DbtestApplication implements CommandLineRunner {

 @Autowired
 TestMapper testMapper;

 public static void main(String[] args) {
  SpringApplication.run(DbtestApplication.class, args);
 }

 @Override
 public void run(String... args) throws Exception {
  System.out.println("Time:" + testMapper.getTime());
 }
}
 수정 내용을 살펴보면 먼저 @MapperScan 어노테이션을 추가해주었다. 여기서 지정된 "com.company.dbtest.mapper" 패키지에서 자동으로 mapper를 찾아서 사용할 것이므로 정확하게 지정하도록 한다.

 이제 TestMapper 인터페이스를 @Autowired로 직접 주입받아서 바로 사용이 가능하다. 위 예에서는 주입받은 testMapper를 run()함수에서 직접 사용하고 있다.

 물론 아직 실행은 안된다. 아직 TestMapper의 getTime()에서 실행할 SQL문을 아직 정의하지 않았기 때문이다. MyBatis가 찾아서 사용할 Mapper XML 파일을 추가해 주어야 한다. 별다른 설정을 하지 않았기 때문에 MyBatis는 mapper 인터페이스와 같은 classpath에서 같은 이름의 xml파일을 찾아서 사용하려고 할 것이다. TestMapper 인터페이스와 같은 classpath(com.company.dbtest.mapper)에 TestMapper.xml 파일을 하나 만들어주고 아래와 같이 정의해 주도록 한다.
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.hansdesk.dbtest.mapper.TestMapper">
    <select id="getTime" resultType="String">
        SELECT NOW()
    </select>
</mapper>
 mapper의 자세한 정의 방법은 MyBatis의 문서를 참고하도록 하고 간단히 설명하자면 mapper 태그의 namespace에 TestMapper 인터페이스의 Full name을 지정하고 select 태그의 id로 메소드의 이름을 지정한 것이다. getTime()이 호출되면 DB에서 "SELECT NOW()"가 실행되어 시간 문자열이 반환된다.

 참고로 IntelliJ를 사용할 때의 주의사항 한 가지를 얘기해야 겠다. xml 파일이 mapper 인터페이스와 같은 패키지에 있으면 MyBatis가 같은 이름의 xml파일을 자동으로 찾아서 사용하지만 IntelliJ에서 디버깅할 때 xml파일이 'src/main/java' 폴더 아래에 있으면 찾지 못한다. 반드시 아래 그림과 같이 'src/main/resources' 폴더에 mapper package와 같은 경로를 만들어서 xml 파일을 넣어놓도록 하자. 'src/main/java' 폴더 아래 mapper 패키지에 xml파일을 넣어놓으면 찾지 못하고 예외가 발생한다. 양쪽 어디에 있건 Jar로 묶어 놓으면 같은 package에 들어가게 되지만 IntelliJ에서 디버깅할 때 만큼은 다르게 인식되는 것 같다.

2017년 7월 17일 월요일

Raspberry Pi에 MariaDB 설치/삭제하기

 Raspberry Pi에도 MySQL과 MariaDB가 모두 설치가능하다. 최신 버전은 아니고 현재 기준으로 MySQL은 5.5, MariaDB는 10.0 버전이 유효하다. (MariaDB가 더 버전이 높지만 base는 MySQL 5.5이다.) 인터넷을 찾아보면 apt-get을 이용하지 않고 직접 소스를 빌드해서 설치한다면 최신 버전도 가능한 것 같기는 한데 같이 설치해야하는 라이브러리가 많아서 쉽지는 않은 모양이다.

 apt-get을 이용하는 방법은 간단하다.
1. 우선 시스템 상태를 최신으로 업데이트하도록 하자. (반드시 필요한 건 아니다.)
$ sudo apt-get update
$ sudo apt-get upgrade
2. apt-get을 이용하여 MariaDB 설치.
$ sudo apt-get install mariadb-server
 설치는 이걸로 끝이다. 어떤 package 들이 설치 되었는 지 확인해보면 아래 그림에서 보듯이 mariadb 이름이 들어간 6개 package가 추가로 설치되어 있다. 물론 이 외에도 필요한 라이브러리들이 아직 미설치된 경우 같이 설치된다. 기본적인 서버 설정도 모두 되어있는 상태이기 때문에 바로 사용이 가능하다. 알려진대로 MariaDB는 MySQL을 대체하기 위한 제품이라 명령들이 거의 호환된다.

 삭제하는 방법도 간단하다. 아래 명령을 차례로 실행하도록 하자.
$ sudo apt-get remove mariadb-server
$ sudo apt-get autoremove
 설치 목록을 확인해보면 mariadb 이름이 들어간 package들은 모두 삭제되었을 것이다. 설치된 목록은 아래 명령으로 확인할 수 있다.
$ apt list --installed
 일단 apt-get으로 설치가 가능한 것들은 설치/삭제가 너무 간단하다. 사실 MariaDB 뿐만 아니라 다른 package들도 다 똑같다. package 이름만 정확하게 알고 있으면 된다. 사실 그래서 더욱 외워두려고 하지도 않고 나중에 또 막상 사용하려고 하면 기억이 안나서 결국 인터넷 검색을 하게된다. 사실 그래서 여기에 기록을 해두려는 의도로 포스팅을 하는 것이다.

 참고로 MySQL을 설치하는 것도 위에서 설명한 명령들의 Package명을 'mysql-server'로 바꿔주면 똑같이 설치되고 삭제된다. 그리고 한 가지 더 참고할 만한 사항을 얘기 하자면 나의 Raspberry Pi에는 MySQL이 설치되어 있었고 작업하던 Database도 있었다. 그 상태에서 MySQL을 MariaDB로 바꾸려고 먼저 설치되어있던 MySQL을 위의 명령들로 삭제하고 바로 MariaDB를 설치했는데 설치 과정 중에 이전 DB를 발견하고 그대로 사용하겠냐는 질문이 뜨길래 'Yes'로 답하고 설치를 완료하였다. 설치가 끝난 후에 접속해보니 모든 데이터가 MySQL을 사용할 때와 똑같은 상태로 남아있었다.

 MariaDB가 MySQL을 base로 만들어졌고 그 둘의 호환성에 대한 많은 얘기를 들었던 터라 어느 정도 예상은 했지만 그래도 약간은 의심이 들었었는데 직접 해보니 MySQL을 사용할 때와 정말 다른 게 없다. 더 깊이 사용해보면 뭔가 다른 점이 나올 지도 모르지만 현재로서는 사용법도 똑같고 라이센스 측면에서 자유로운 MariaDB로 갈아타는 게 괜찮은 선택이라고 생각된다.

2017년 7월 16일 일요일

Spring Boot에서 Logback 설정 파일 지정하기

 전에 IntelliJ IDEA CE 버전으로 Spring Boot 프로젝트를 생성하는 방법을 설명했었다. 그 방법으로 Spring Boot 프로젝트를 최소한의 dependency만 설정한 채로 생성을 하면 'spring-boot-starter'에 대한 dependency만 가진 채로 생성이 된다.

 이 상태로 Project 뷰에 가서 External Libraries를 열어보면 Logback과 SLF4J 라이브러리에 대한 dependency가 이미 포함되어있는 걸 알 수 있다. 즉, 추가 dependency 설정 없이 이미 Logback을 사용할 수 있는 상태라는 뜻이다. JCL, JUL, Log4J 같은 다른 Logger들을 SLF4J로 연결해주는 브릿지 라이브러리들도 모두 이미 들어가 있으니 Logback을 위한 dependency 설정은 제대로 되어있다고 볼 수 있다.

 Spring Boot에서 Logback 설정 파일은 일반적으로 logback-spring.xml 파일을 만들어서 classpath 루트에 두면 된다. 즉 'src/main/resources'에 logback-spring.xml 파일을 만들어서 넣어두면 알아서 찾아서 적용이 된다. 하지만 'src/main/resources'에 넣어두는 파일은 jar 배포시 jar안에 들어가게 되기 때문에 매번 실행 전에 로그 레벨 등의 설정을 바꿔서 적용하겠다는 의도로는 적합하지 않은 방법이다.  그렇게 하려면 로그 설정 파일은 jar파일 외부에 따로 존재하도록 해야한다.

 로그 설정 파일을 따로 만들어 두고 그 파일을 Spring Boot에서 읽어가도록 하는 방법은 application.properties 파일에 로그 설정 파일을 지정하는 것이다. 아래와 같이 logging.config 속성을 정의해주면 그 속성에 지정된 파일을 찾아서 로그 설정 파일로 이용한다.
# application.properties
logging.config=logback.xml
 로그 설정파일의 이름도 logback-spring.xml이 아닌 다른 원하는 이름으로 지정해서 사용해도 문제가 없다. 위 설정에서는 jar를 실행하는 폴더에 있는 logback.xml 파일이 설정파일로 이용될 것이다.

 위 설정으로 배포를 하게되면 반드시 logback.xml 파일이 jar를 실행하는 폴더에 같이 있어야 한다. 만약 없으면 로그 기능은 동작하지 않게 되고 설정 파일이 없다는 불평을 보게될 것이다. 이렇게 되면 배포할 때 항상 logback.xml 파일도 같이 배포해야 하는 부담이 생긴다. 왠지 설정 파일이 없으면 default로 적용되는 파일이 따로 있어서 반드시 같이 배포해야 하는 부담이 없었으면 좋겠다는 생각이 들 것이다.

 default 로그 설정 파일을 만들어서 jar에 포함시키고 외부에 파일이 있을 때는 그 파일을 지정할 수 있으면 된다.

 먼저 default로 적용될 로그 설정 파일을 만들자.  설정 파일 이름은 'logback.xml'이라고 하겠다.
1. jar에 포함시킬 default 로그 설정 파일은 'src/main/resources' 폴더에 만들어서 넣어둔다.
2. 'src/main/resources' 폴더에 'application.properties'에 아래 설정을 추가한다.
# application.properties in 'src/main/resources'
logging.config=classpath:logback.xml
 사실 별 내용은 없다. classpath에 있는 logback.xml 파일을 default로 사용하도록 설정한 것이다. 이 파일들은 jar 안에 들어있기 때문에 jar만 배포했을 때 jar 안에 있는 설정 파일들이 적용된다.

 이 상태에서 외부에 있는 설정 파일들을 적용하려면? 그냥 jar를 실행하는 폴더에 application.properties 파일을 두고 거기에 처음에 설명한 것처럼 외부 설정파일을 지정하면 된다. jar 외부에 있는 application.properties 파일이 더 우선순위가 높기 때문에 그곳에 지정한 파일이 적용된다.

2017년 7월 14일 금요일

Spring Boot의 application.properties에 대해..

 요즘 Spring Boot를 이용해서 Non-web application을 만드는 작업에 빠져서 Spring에 대해 많은 걸 배우고 공부하는 중이다. 그래서 이번에는 Spring Boot application을 위한 설정파일로 가장 간단하게 이용할 수 있는 방법인 application.properties를 이용하는 방법을 간단히 설명해 보려고 한다.

 Spring Boot를 이용할 때 대부분의 경우 설정 파일로 application.properties를 이용한다. application.properties는 기본적으로 Spring Boot가 읽어들이도록 되어있기 때문에 사실 파일만 만들어서 'src/main/resources' 폴더에 넣어주면 바로 설정파일로 이용이 가능하다. 이렇게 편한 방법이 있는데 따로 만들어서 사용할 이유가 없다.

 간단한 사용법을 보자.

# application.properties
name=Michael
import org.springframework.stereotype.*
import org.springframework.beans.factory.annotation.*

@Component
public class MyBean {

    @Value("${name}")
    private String name;

    // ...

}
 위의 MyBean 클래스의 name 멤버변수는 @Value 어노테이션을 지정한 것만으로 application.properties에 있는 name속성 값 "Michael"로 초기화가 된다.

 만약에 application.properties 파일에 name 속성이 정의되어있지 않았을 때 default값을 지정하는 방법도 있다. 아래와 같이 @Value 어노테이션에서 속성 이름 옆에 콜론(:)을 찍고 직접 지정해 주면 된다.

    @Value("${name:Michael}")
    private String name;

    @Value("${age:20}")
    private int age;
 위 예에서 지정된 default 값들은 application.properties에 값이 정의되어있지 않으면 각 변수의 타입에 맞게 변환되어 대입된다.

 사실 Spring Boot의 문서를 보면 property source를 설정하는 다양한 방법이 제공된다.

  1. Devtools global settings properties on your home directory (~/.spring-boot-devtools.properties when devtools is active).
  2. @TestPropertySource annotations on your tests.
  3. @SpringBootTest#properties annotation attribute on your tests.
  4. Command line arguments.
  5. Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable or system property)
  6. ServletConfig init parameters.
  7. ServletContext init parameters.
  8. JNDI attributes from java:comp/env.
  9. Java System properties (System.getProperties()).
  10. OS environment variables.
  11. A RandomValuePropertySource that only has properties in random.*.
  12. Profile-specific application properties outside of your packaged jar (application-{profile}.properties and YAML variants)
  13. Profile-specific application properties packaged inside your jar (application-{profile}.properties and YAML variants)
  14. Application properties outside of your packaged jar (application.properties and YAML variants).
  15. Application properties packaged inside your jar (application.properties and YAML variants).
  16. @PropertySource annotations on your @Configuration classes.
  17. Default properties (specified using SpringApplication.setDefaultProperties).
 위의 목록은 더 높은 우선순위를 갖는 property source부터 차례대로 나열한 것이다. 여기서 소개하는 application.properties 파일을 'src/main/resources' 폴더에 넣는 방법은 위의 목록 중 15번에 나오는 방법으로 우선순위로 보면 상당히 아래쪽에 나온다. 즉 상당히 우선순위가 낮다는 말이다. 'src/main/resources' 폴더에 application.properties 파일을 넣어 놓는다는 것은 배포 시에 jar 파일에 함께 묶여서 배포가 된다는 것이고 따라서 배포 후에 설정 값을 변경하려는 의도로 사용하기에는 적합하지 않다. 그러한 용도로 사용하려면 14번에서 말하는 jar 외부의 application.properties를 사용하면 된다. 그냥 application.properties 파일을 따로 만들어서 jar를 실행하는 directory에 두면 된다. jar 안에 있는 application.properties 파일보다 우선순위가 높기 때문에 둘 다 있는 경우 jar 외부의 application.properties 파일의 값이 더 높은 우선순위를 갖는다.

 여기서 많은 사람들이 우선순위에 대해서 오해하는 점 하나를 짚고 가야겠다. 예를 들어 jar안에 같이 포함된 application.properties파일과 jar 외부의 application.properties 파일이 둘 다 존재할 때 외부의 application.properties 파일이 더 우선순위가 높기 때문에 내부의 application.properties 파일이 완전히 무시된다고 생각하기 쉽다. 하지만 그렇지 않다. 우선순위가 높은 property source의 값들은 더 낮은 property source의 값들을 덮어쓰는 것이지 낮은 우선순위의 property source를 읽어들이지 않는 다는 뜻이 아니다. 즉 둘 다 name 속성이 정의되어있다면 더 높은 우선순위의 property source에 선언된 name 속성 값이 사용된다. 하지만 age라는 속성이 더 낮은 우선순위의 property source에만 선언되어있다면 낮은 우선순위의 property에 선언되었지만 그 값이 사용된다. (더 높은 우선순위에 값이 정의되지 않았으므로)

 즉 위 목록의 모든 property source들을 아래쪽부터 윗쪽으로 차례대로 다 찾아서 읽어들이면서 이미 정의된 property들은 덮어 쓰는 개념이다. 목록을 보면 테스트 환경을 제외하고 command line argument가 가장 우선순위가 높으므로 임시로 특정 값을 override하고 싶다면 command line을 이용해서 property를 전달하면 무조건 전달된 값이 이용될 것이다.

 command line을 이용하는 방법은 아래와 같은 방법으로 전달하면 된다. ('server.port' 속성값 지정)

$ java -jar myapp.jar --server.port=9000
 위 목록에 나열된 방법은 많지만 흔히 사용하는 방법으로 이 정도만 알고 있으면 대부분의 경우 문제없이 개발을 진행할 수 있다. 개념적으로 잘 이해하고 있다면 다른 나머지 방법을 이용하는 것도 사실 시간문제일 뿐 크게 어려운 부분은 없을 것이다.

2017년 7월 4일 화요일

IntelliJ IDEA CE + Spring Boot + Gradle 프로젝트 생성하기

 IntelliJ IDEA CE(Community Edition)버전을 이용해서 Spring Boot 프로젝트를 수행하는 데 이용할 수는  없을까해서 이런 저런 시도를 해보았다. Gradle을 이용해서 프로젝트를 수행한다면 어차피 IntelliJ IDEA CE 버전도 문제가 없을 것 같았기 때문이다. 그래서 여러가지 시도를 해보고 가장 최신 버전의 Spring Boot를 이용하면서 IntelliJ스러운 프로젝트를 생성하는 방법을 여기에 기록해볼까 한다.

 최신 버전의 Spring Boot를 이용하는 Gradle 프로젝트 파일을 생성하는 건 역시 "Spring Initializr"를 이용하는 것이다. 일단 https://start.spring.io에 접속해서 프로젝트를 생성하자.

 한 화면으로 생성이 끝나는 간단한 작업이다. 각 선택 항목들도 모두 이해못할 것들은 없다. 중요한 건 빨간색 사각형 부분들이다. Gradle Project로 생성하고 있는 지 확인하도록 하자.  그리고 Group과 Artifact는 Java의 root package이름이 되므로 예제처럼 Group은 (company의) reverse domain 형태로 넣어주고 Artifact는 프로젝트 이름을 소문자로 넣어주자. 나중에 IntelliJ에서 프로젝트를 생성할 때는 "Group.Artifact"를 Group ID로 사용할 것이다.

 "Switch to the full version."링크를 누르면 dependency를 추가하는 등의 세부적인 작업을 할 수 있다. 하지만 추후에도 얼마든지 가능한 작업들 이므로 여기서는 간단한 설명을 위해 그냥 최소한의 설정으로 생성하겠다.

 "Generate Project" 버튼을 누르면 프로젝트가 zip으로 묶여서 다운로드된다. 일단 파일을 원하는 곳에 풀어두도록 하고 이제 IntelliJ를 실행해서 새로운 프로젝트를 만들자.

 "Create New Project"를 눌러서 새로운 프로젝트를 만들자.

 Gradle > Java 를 선택하고 Next를 눌러 다음으로 진행한다.

 GroupId 항목에는 Spring Initializr에 넣었던 Group과 Artifact를 그림처럼 연결해서 넣어주고 ArtifactId는 원하는 Project 이름을 넣어준다. 

 GroupId 항목은 build.gradle 파일의 group 정보로 들어가게 되고 Java의 루트 패키지가 될 것이다. ArtifactId는 프로젝트 이름으로서 그리고 Module 이름으로서 사용이 된다.

 이후는 일반적인 Gradle Java 프로젝트와 같다. 그림과 같이 선택해주고 Next를 눌러 다음으로 넘어가자.

 프로젝트 위치를 원하는 곳으로 설정하고 Finish를 눌러서 프로젝트 생성을 완료하자.

 몇 초 정도 생성작업을 하고나서 아래 그림과 같은 구조로 프로젝트가 생성된다. (왼쪽 프로젝트 뷰의 트리구조 참고)

 IntelliJ에서 프로젝트를 생성하고 나서 build.gradle 파일을 열어보면 위의 그림과 같이 간단한 내용으로 생성되어있다. "Spring Initializr"에서 생성한 프로젝트의 build.gradle과 비교해보면 빨간색으로 표시한 group 정보 말고 나머지 내용은 모두 이미 존재한다는 걸 알 수 있다. 

 대충 짐작이 갈 것이다. IntelliJ에서 생성한 build.gradle 파일에서 group 정보만 남기고 나머지는 모두 삭제하고 Spring Initializr 에서 생성한 build.gradle 파일의 내용으로 대체하도록 한다. 그리고나서 Spring Initializr가 생성한 프로젝트의 src 폴더를 IntelliJ가 생성한 프로젝트로 옮겨서 덮어쓰면 끝이다. 최종적으로 아래 그림과 같은 프로젝트가 될 것이다. 

 build.gradle 파일의 내용은 Spring Initializr가 생성한 그대로의 내용에 group 정보만 넣은 것과 같고 src 폴더를 보면 Spring Initializr가 생성한 @SpringBootApplication 클래스와 테스트 클래스가 보인다. 

 간단히 요약하자면 IntelliJ가 생성한 Gradle Java 프로젝트를 Spring Initializr가 생성한 프로젝트의 내용으로 덮어씌우는 작업이다. 단지 Group, Artifact 정보를 package 경로와 맞추기 위해서 몇 가지만 맞춰주면 되는 것이다. 

 IntelliJ IDEA CE에서 Import를 하는 방법으로 가져와 봤지만 위와 같은 구조로 생성이 되지 않는 것으로 결론을 냈다. 난 IntelliJ가 생성하는 Gradle Java 프로젝트의 기본 구조가 마음에 든다. 그리고 Spring Intializr가 생성하는 최신 build.gradle 파일도 믿음이 간다. 그래서 이런 방법을 사용하는 것이다. 

 참고로 Runnable Jar 파일을 생성하는 방법을 설명하자면 Gradle뷰에서 보이는 태스크들 중에서 'build'를 더블클릭해서 실행하는 것이다. 'build' 태스크를  build/libs/ 폴더 아래 Runnable Jar가 생성된다. build.gradle 파일에 java 플러그인이 적용되었기 때문에 'jar' 태스크가 있지만 이 'jar' 태스크는 build.gradle을 추가로 수정하지 않으면 dependency가 있는 라이브러리까지 묶어놓은 Runnable Jar를 만들지는 못하므로 반드시 'build' 태스크를 이용하도록 하자.

2017년 7월 1일 토요일

IntelliJ로 Gradle Java Project 생성부터 Runnable Jar 생성 까지

 안드로이드 개발 환경으로 Android Studio가 대세가 되다보니 이제 IntelliJ IDEA가 Java 개발에서도 많이 사용을 하게되고 더불어 Gradle도 빌드툴 분야에서 점점 더 입지를 넓혀가는 중이라 나도 Java 개발 환경으로 IntelliJ와 Gradle을 사용해 보려고 여러가지 테스트를 진행 중이다.

 그 중에서도 배포를 위한 Runnable Jar 파일을 생성하는 방법이 좀 까다로워서 여기에 그 방법을 남겨볼까 한다. 간단한 프로젝트를 하나 만들어서 Jar를 생성하는 것까지 최대한 간단하게 설명을 해보도록 하자.

 일단 IntelliJ를 실행하고 Create New Project를 선택해서 Gradle Java 프로젝트를 생성해 보자.

 Create New Project를 선택하면 아래와 같은 창이 뜨는데 왼쪽 뷰에서 Gradle을 선택하고 오른쪽 뷰에서 Java를 체크하고 Next를 눌러 다음으로 넘어가자.

그러면 Gradle 프로젝트 생성을 위한 기본 정보를 입력하는 창이 나오는데 Group ID와 Artifact ID를 적당히 원하는 값으로 넣어준다. 그림을 보면 Group ID와 Artifact ID의 일반적인 형식이 무엇인지 알 수 있을 것이다. 필수는 아니지만 적당히 맞춰서 넣어주는 게 좋겠다. Next를 눌러 다음으로 넘어가자. 

 다음으로 아래와 같은 창이 뜨면 대부분 기본적인 설정이 되어있지만 'Create directories for empty content roots automatically' 체크 박스만 추가로 더 해주도록 하자. 이렇게 해주면 기본적인 Java 소스 폴더 구조를 생성해준다. 그 구조를 사용하지 않을 계획이라면 필요없다. 'Use auto-import' 옵션은 뒤에 dependency를 추가할 때 다시 물어보기 때문에 반드시 필요하지는 않지만 여기서 체크를 해주는 게 편하다. (그림에서는 안했지만..) 나머지 옵션들은 Gradle이 권장하는 사용 패턴대로 되어있으므로 그대로 사용해도 된다. Next를 눌러 다음으로 넘어가자.

 마지막으로 프로젝트 이름과 생성 위치를 선택하고 Finish를 누르면 몇 초 정도 후에 생성이 완료된다.

 생성 완료 후에는 아래 그림의 왼쪽 프로젝트 뷰의 구조와 같은 형태가 된다. (메뉴 때문에 조금 가려졌지만 보이는 부분이 전부다)

 src -> main -> java 폴더에 우클릭해서 New > Java Class를 선택하면 클래스 생성 창이 뜨는데 적절히 클래스 이름을 입력하고 OK를 눌러 완료하면 클래스 파일 소스가 생성되어있다.

  생성된 파일을 더블클릭해서 열고 간단히 몇 줄 넣어보자. 

 실행하고 디버깅하는 방법은 일반 다른 IDE와 비슷하므로 메뉴를 좀 둘러보면 알 수 있으므로 여기서 설명은 하지 않고 바로 Runnable Jar 생성을 해보도록 하겠다.

 일반적으로 IntelliJ를 이용해서 jar를 생성할 때는 File > Project Structure... > Artifacts를 통해서 Artifact로 Jar를 하나 등록한 후 Build > Build Artifacts... 메뉴를 통해서 생성하는 방법이 많이 소개되어있다. 하지만 이 방법을 사용하면 IntelliJ의 버그때문인지 MANIFEST 파일을 생성하는 데 문제가 있고(인터넷에 많이 소개되어있으므로 자세한 설명은 생략) 그 문제를 피하더라도 dependency가 있는 외부 라이브러리를 같이 jar에 패키징하려고 하면 그 라이브러리의 MANIFEST 파일이 최종적으로 Jar에 남아서 실행이 안되는 문제가 있다.

 그러나 지금은 Gradle을 이용해서 프로젝트를 생성하였으므로 Gradle을 이용해서 Jar를 생성하면 아무 문제가 없다. Gradle 프로젝트이므로 당연히 이렇게 사용하는 게 더 맞을 것이다. 그 방법을 여기서 설명하려고 한다.

 기본적으로 Gradle 프로젝트를 생성하면 위 그림의 내용으로 build.gradle 파일이 생성되어 있다. 이 파일이 Gradle을 이용해서 빌드를 수행하기 위한 스크립트 파일이며 필요한 정보들이 최소한으로 설정되어있다. 간단히 설명하자면 'java' 플러그인이 사용되고 있고, repositories 항목의 mavenCentral()은 maven 으로부터 각종 dependency가 있는 라이브러리들을 가져오도록 설정한 것이고, dependencies 항목에 필요한 라이브러리들을 설정함으로써 maven 에서 필요한 것들을 가져오게 될 것이다.  지금은 test를 위한 컴파일 시에 junit 프레임워크가 필요하다고 설정한 것이다. (기본적으로 설정 되어있다) 나머지 버전 정보 등은 설명하지 않아도 간단한 것들이므로 보면 알 수 있을 것이다.

 위 내용은 기본적으로 생성되는 최소한의 상태다. 여기서 Jar 생성을 위해서 Manifest 정보만 아래와 같이 추가해주면 끝이다.


 아래 그림처럼 윈도우 우측의 Gradle 뷰를 열고 jar 항목을 더블클릭하면 Jar 빌드가 진행되고 최종적으로 왼쪽 프로젝트 뷰의 build -> libs 폴더 아래 jar 파일이 생성되어 있음을 확인할 수 있다. 터미널에서 직접 실행해보고 제대로 되는 지 체크해보기 바란다.

 이렇게 Gradle을 이용하는 방법은 dependency가 있는 라이브러리를 같이 jar로 묶어서 배포하려고 할 때도 이용할 수 있다. 간단하게 log4j2 라이브러리를 추가하고 같이 패키징 해보도록 하자.

 먼저 dependencies 항목에 아래 오른쪽 사각형과 같이 추가해주도록 하자. 간단히 설명하자면 compile할 때 이 라이브러리가 필요하다는 것을 Gradle 형식에 맞게 추가해 준 것이다. 참고로 두 개의 라이브러리를 등록한 것이다.

 이렇게 추가하면 auto import 옵션이 enable된 상태라면 자동으로 Gradle이 필요한 라이브러리를 maven으로부터 가져와서 왼쪽 사각형과 같이 프로젝트에 추가해준다. (정말 좋은 세상이다.) auto import가 꺼져있다면 작은 툴팁 같은 창이 우측 하단에 뜨면서 enable하겠냐고 묻는데 이 때 enable해도 된다. 가져오기가 완료되면 왼쪽 프로젝트 뷰의 External Libraries에 추가된 라이브러리가 보일 것이다.

 이제 build.gradle 파일을 아래와 같이 수정해준다.

 compile시에 필요한 파일들을 같이 패키징하기 위한 스크립트다. 디렉토리인 경우 그대로 넣고 jar인 경우 풀어서 넣는다. 많은 사람들이 소개하는 방법이지만 정확한 사용법은 Gradle과 Groovy를 제대로 사용할 줄 알아야 알 수 있을 것 같다. 앞에서 dependencies항목에 compile dependency로 jar 2개를 추가했으므로 추가된 jar 파일들의 내용물이 같이 포함되어 패키징될 것이다.

 다시 Gradle 뷰에서 jar를 더블클릭하여 jar파일 생성해보자. 생성된 jar 파일의 크기를 보면 이전에 몇 백 바이트 정도의 크기였던 jar 파일이 메가 단위로 커졌을 것이다. 생성된 Jar 파일의 내용을 보면 추가된 라이브러리의 .class 파일들이 잔뜩 들어가 있는 것을 알 수 있다. 물론 여기서는 HelloWorld 코드에서 직접 log4j 라이브러리를 사용하지는 않았지만 당연히 사용에 문제가 없다. 혹시 궁금하다면 직접 HellowWorld를 로그로 출력하는 코드를 테스트 해보기 바란다.

 참고로 jar 파일 안에 포함된 파일의 리스트를 보는 명령은 'jar -tf 파일이름.jar'이다. 압축을 풀어서 확인하고 싶다면 'jar -xvf 파일이름.jar' 명령을 실행하면 현재 폴더에 압축을 푼다.