그랄VM(Graalvm) Spring Boot Mssql 세팅하는 방법 (윈도우OS)
```
How to set up GraalVM with Spring Boot and MSSQL (Windows OS)
```
이번 글을 통해 배워갈 내용
- Graalvm 설명
- Spring Boot 프로젝트 세팅
- Graalvm 실행파일 생성(윈도우)
- Graalvm 실행파일 생성(리눅스)
- 참조
Graalvm 설명
1줄 요약
JVM에 비해 메모리 사용량이 적고 부팅 시간이 빠르지만, 최신 기술인 만큼 JVM만큼의 안정성은 아직 갖추지 못했습니다.
GraalVM의 유래
"Graal"이라는 단어는 고대 프랑스어에서 유래하며 "Grail"을 의미합니다.
GraalVM 컴파일러
GraalVM은 기존 JVM에 추가된 Java Just-In-Time (JIT) 컴파일러인 Graal을 포함합니다. 이 컴파일러는 Java뿐만 아니라 Kotlin, Groovy, R, Python, Javascript 등 다양한 언어를 추가로 지원합니다. GraalVM은 자바 코드를 자바를 설치하지 않고도 실행할 수 있게 도와주며, AOT 컴파일러를 통해 워밍업 시간을 단축시킵니다.
GraalVM 네이티브 이미지
GraalVM은 Java 파일을 exe 파일이나 다른 네이티브로 실행 가능한 바이너리 파일로 변환할 수 있는 기능을 제공합니다. 이를 통해 Truffle 프레임워크를 사용하여 개발된 동적 언어를 실행할 수 있으며, 필요한 경우 Substrate VM에서 사용할 수 있습니다.
JVM 대비 GraalVM의 장점
언어 중립적 도구 지원:
GraalVM은 VM 런타임에서 직접 완전히 동적인 도구를 지원합니다. 이를 통해 실행 이벤트를 매우 낮은 오버헤드로 캡처할 수 있습니다.
폴리글랏(Polyglot) 애플리케이션:
Java 소스 코드 내에서 JavaScript, Python 또는 다른 지원되는 언어의 코드를 통합할 수 있습니다.
빠른 시작:
GraalVM 네이티브 이미지는 클래스 로딩이나 인터프리터 및 JIT 컴파일러의 초기화가 필요 없어 훨씬 빠르게 시작할 수 있습니다.
낮은 메모리 사용량:
네이티브 이미지로 컴파일 시 JVM 기능, 클래스 메타데이터 및 JIT 구조를 제거하여 메모리 사용량을 줄일 수 있습니다.
성능 향상의 미래:
'Isolates'와 같은 기능이 GraalVM에서 개발 중이며, 이는 향후 성능을 더욱 개선할 수 있습니다.
JVM에 비해 GraalVM의 단점
일부 JVM 기능 지원 부족:
네이티브 이미지는 JVMTI, Java 에이전트, JMX, JFR 같은 중요한 JVM 기능을 지원하지 않습니다.
큰 힙 크기에 대한 효율성 감소:
네이티브 이미지에서 사용되는 GC의 특성으로 인해 큰 힙을 사용하면 성능에 부정적인 영향을 줄 수 있습니다.
Spring Boot 프로젝트 세팅
먼저 스프링이니셜라이저를 실행해서 세팅합니다
application.properties
# DB
spring.jpa.hibernate.ddl-auto=none
spring.datasource.url=${KIMC_DB_URL}
spring.datasource.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver
spring.datasource.pool-name=${KIMC_DB_POOLNAME}
hibernate.dialect=org.hibernate.dialect.SQLServerDialect
spring.jpa.show-sql=true
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
# Hikari
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.idle-timeout=600000
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=10
spring.datasource.hikari.pool-name=ConnPool
# ETC
server.port=${KIMC_PORT}
spring.jpa.open-in-view=false
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.petclinic</groupId>
<artifactId>petclinic</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>petclinic001</name>
<description>petclinic001</description>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<configuration>
<buildArgs combine.children="append">
<buildArg>--initialize-at-build-time=org.apache.commons.logging.LogFactory,org.apache.commons.logging.LogFactoryService,org.slf4j.MDC,ch.qos.logback.core.pattern.parser.Parser,ch.qos.logback.core.util.Loader,ch.qos.logback.core.util.StatusPrinter,org.slf4j.impl.StaticLoggerBinder,org.slf4j.LoggerFactory,ch.qos.logback.classic.Logger,ch.qos.logback.core.spi.AppenderAttachableImpl,ch.qos.logback.core.status.StatusBase,ch.qos.logback.classic.Level,ch.qos.logback.core.status.InfoStatus,ch.qos.logback.classic.PatternLayout,ch.qos.logback.core.CoreConstants</buildArg>
<buildArg>-H:+ReportExceptionStackTraces</buildArg>
<buildArg>--enable-url-protocols=http</buildArg>
<buildArg>-H:+AddAllCharsets</buildArg>
</buildArgs>
<jvmArgs>
<arg>-Xmx8g</arg>
<arg>-Xms8g</arg>
</jvmArgs>
</configuration>
<executions>
<execution>
<id>build-native</id>
<goals>
<goal>compile-no-fork</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
</configuration>
</plugin>
<!-- <plugin>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-maven-plugin</artifactId>-->
<!-- <configuration>-->
<!-- <image>-->
<!-- <buildpacks>-->
<!-- <buildpack>gcr.io/paketo-buildpacks/graalvm</buildpack>-->
<!-- <buildpack>gcr.io/paketo-buildpacks/java-native-image</buildpack>-->
<!-- </buildpacks>-->
<!-- <env>-->
<!-- <BP_JVM_VERSION>21.0.1</BP_JVM_VERSION>-->
<!-- <BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>-->
<!-- </env>-->
<!-- </image>-->
<!-- </configuration>-->
<!-- </plugin>-->
</plugins>
</build>
</project>
Dockerfile-native-image ( for linux)
FROM ubuntu:latest
COPY target/petclinic /petclinic
RUN chmod +x /petclinic
ENTRYPOINT ["/petclinic"]
controller
package com.petclinic;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PetController {
private final PetService petService;
public PetController(PetService petService) {
this.petService = petService;
}
@GetMapping("/hello")
public String home() {
return "Hello Docker World ";
}
@GetMapping("/pets")
public String getPets() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(petService.getPets());
}
}
entity
package com.petclinic;
import jakarta.persistence.*;
@Entity
@Table(name = "pets")
public class PetEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name", nullable = false)
private String name;
@Column(name = "type")
private String type;
@Column(name = "age")
private Integer age;
// Add Constructors, getters, and setters
}
service
package com.petclinic;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
@Service
public class PetService {
private final PetRepository petRepository;
public PetService(PetRepository petRepository) {
this.petRepository = petRepository;
}
@GetMapping("/pets")
public Iterable<PetEntity> getPets() {
return petRepository.findAll();
}
}
repository
package com.petclinic;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface PetRepository extends CrudRepository<PetEntity, Long> {
}
Graalvm 실행파일 생성(윈도우)
로컬에서 native 컴파일을 해서 Graalvm 이미지를 생성합니다
mvn -Pnative native:compile
target 폴더에 생성된 exe 파일을 실행하면
graalvm 으로된 스프링 프로젝트가 실행됩니다
Graalvm 실행파일 생성( 리눅스 )
mvn -Pnative native:compile
원래는 아래와 같이 도커 이미지를 활용해서
이미지상에 Graalvm을 만들 계획이였습니다만
이미지 자체에 호환성 문제때문에 컴파일 오류가 발생해서
다른 방법을 사용하였습니다
<!-- 튜토리얼용 쓰지 않는 코드 시작 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<buildpacks>
<buildpack>gcr.io/paketo-buildpacks/graalvm</buildpack>
<buildpack>gcr.io/paketo-buildpacks/java-native-image</buildpack>
</buildpacks>
<env>
<BP_JVM_VERSION>21.0.1</BP_JVM_VERSION>
<BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
</env>
</image>
</configuration>
</plugin>
<!-- 튜토리얼용 쓰지 않는 코드 끝 -->
윈도우 Hyper-V 우분투에서 이미지 빌드를 하였습니다
Hyper-V에 대해서 알고싶으신 분은
아래에서 Hyper-V 세팅을 배우시면 됩니다
https://codemasterkimc.tistory.com/630
sdkman 설치
curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"
sdk version
graalvm 설치
sdk install java 21.0.1-graal
https://www.graalvm.org/downloads/
mvn 설치
sudo apt-get -y install maven
mvn -version
gcc 설치
sudo apt install build-essential
gcc --version
zlib Development Package 설치
sudo apt-get install zlib1g-dev
dpkg -L zlib1g
리눅스 실행파일 생성
mvn -Pnative native:compile
도커 이미지 생성(리눅스 컨테이너 도커)
리눅스에서 실행파일이 정상 생성되었다면
해당되는 실행파일을 가지고 도커 이미지 생성을 해줍니다
docker build . --tag kimc/dockerhubsample --file Dockerfile-native-image
도커 컨테이너 실행
이미지를 가지고 컨테이너를 실행해줍니다
docker run -d -p 5000:5000 -p 80:80 -e "KIMC_PWD=mypasswordsample" kimc/dockerhubsample
필요시 해당 이미지를 도커허브에 배포합니다
docker push kimc/dockerhubsample
에러 핸들링01
com.microsoft.sqlserver.jdbc.SQLServerException: Windows collation ko_KR is not supported by this driver.
mvn -Pnative native:compile 실행시
위와 같은 에러가 발생했다면 pom.xml에 -H:+AddAllCharsets
을 추가해서 해결 가능합니다
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<configuration>
<buildArgs combine.children="append">
<buildArg>--enable-url-protocols=http</buildArg>
<buildArg>-H:+AddAllCharsets</buildArg>
</buildArgs>
</configuration>
</plugin>
에러 핸들링02
java.lang.RuntimeException: Driver com.microsoft.sqlserver.jdbc.SQLServerDriver claims to not accept jdbcUrl
jdbc:server:// url 과 id, 비밀번호를 다시 확인해보기
참조
https://github.com/oracle/graal/issues/6764
https://mydeveloperplanet.com/2023/03/29/how-to-create-a-graalvm-docker-image/
읽어주셔서 감사합니다
무엇인가 얻어가셨기를 바라며
오늘도 즐거운 코딩 하시길 바랍니다 ~ :)