GraalVMのnative-imageを少し試してみる

GraalVMのnative-imageがそろそろ色々使えそうなので、試してみる。

Javaやってる人は SDK Man は入ってると思うので、それ前提でメモします。
(SDK Man のインストールはこちら -> https://sdkman.io/ )

GraalVM のインストール

まずは、GraalVMのインストールを行う。

$ sdk install java 20.0.0.r11-grl

GraalVMを使ってnative-imageをinstallする

GraalVMのパッケージツールでnative-imageをinstallする。

$  gu install native-image

これでnative-imageのinstallが完了。

かんたんなJavaコードを書く

Sample.java

class Sample {
  public static void main(String[] args) {
   System.out.println("Hello World");
  }
}

Javaをコンパイルする。

$ javac Sample.java

コンパイルすると、classファイルができる。

Sample.class

native-imageでコンパイルする


$ native-image Sample sample // 最後のファイル名を指定しないと、デフォルトで小文字になる

下記、ファイルができる。

Sample

このファイルは実行できる。

./sample

比較する

JavaVMでの実行とnative-imageでの実行を比較する

$ time ./sample
real    0m0.007s
user    0m0.003s
sys     0m0.003s

======

$ time javac Sample
real    0m0.179s
user    0m0.110s
sys     0m0.035s

JavaVMの起動速度が入ってしまっていると思うが、結構違う。
JVMの起動後にどのくらい違うのかはまた次回。
あと、こんな1ファイルで使う人もいないと思うので、Jar での作成方法もまた次回。

MyBatisが色々てんこ盛りになっていい感じになっている件

久しぶりにSpringBootの環境構築をやろうかと思い、ORMをどうするか選定していたところ、MyBatisが結構進化していたので、メモ。

MyBatis3DynamicSql

  • XMLが不要になる
  • コード量が小さくなる
  • JavaでタイプセーフにSQLがかける(JOOQみたい)
  • https://mybatis.org/generator/quickstart.html

こんな感じでかけます。

        List<TableCode> records = mapper.selectByExample()
                .where(id, isEqualTo(3))
                .build()
                .execute();       

        List<TableCode> records = mapper.selectByExample()
                .where(id, isEqualTo(3))
                .or(description, isLike("f%"))
                .build()
                .execute();    

        List<TableCode> records = mapper.selectByExample()
                .where(id, isLessThan(10), and(description, isEqualTo("foo")))
                .or(description, isLike("b%"))
                .orderBy(id.descending())
                .build()
                .execute(); 

JOOQみたいに、タイプセーフでSQLがかけるのはとてもすごい進化だと思います。

FreeMarker Tempalte

前に書いたとおりのFreeMarker Templateは健在。XMLがいやでFreeMarkerにしたけど、複雑なクエリはやはりJavaコードじゃなくて、素のSQLの方がいいイメージなので、継続して使用します。

sample.ftl

select
 *
from
  sample_table
where
  id = <@p name="param.id" />

こんな感じでかける。

from句とかを別のftlファイルにして、includeして使うことも可能です。

Thymeleaf Template

Doma2とかでやっている2WaySQLがThymeleaf Templateで実現している。Doma2より書き方は野暮ったいけど、一応2waySQLはできる。

SELECT * FROM names
  WHERE 1 = 1
  /*[# th:if="${not #lists.isEmpty(ids)}"]*/
    AND id IN (/*[# mb:p="ids"]*/ 1 /*[/]*/)
  /*[/]*/
  ORDER BY id

MyBatis Migrate

これは前からあったみたいなんだが、Flywayの陰に隠れて知らなかった。
機能的には十分っぽいので、試してみる。

詳細 => https://mybatis.org/migrations/

Mybatis Log Plugin (Intellijのみ)

これがあると、条件分岐があって、引数のバインドが全部終わったSQLを出力してくれるので、デバッグに便利です。
必需品かと思います。(Domaは標準でありますが)

https://plugins.jetbrains.com/plugin/10065-mybatis-log-plugin

最後に

タイプセーフでSQLかけるし、2WaySQLあるし、Migrateあるし、なんかMyBatisで全部OKなんじゃないかとおもいました。おそらくThymeleafは

Intellijでdebug実行すると warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended

warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended

ブートストラップクラスパスが追加されているため、共有はブートローダークラスでのみサポートされます

ということらしいが、intellijでしか警告がでない。

Intellij Idea > preference > Build, Execution,Deployment > Async Stack TraceInstrumenting agent をunlockすれば、でなくなる。

intellijでdebugするとこの警告がでてくる warning: forcing TieredStopAtLevel to full optimization because JVMCI is enabled

OpenJDK 64-Bit GraalVM CE 19.3.0 warning: forcing TieredStopAtLevel to full optimization because JVMCI is enabled

JVMCIが有効になっているため、TieredStopAtLevelを完全に最適化する こういうことらしいが、gradle bootRun では発生しない。Intellijで発生する。

IntelliJの設定かなにかだろうと思い設定を確認

Run/Debug Configurations の中に Enable launch optimization があり、これをunlockすると警告がでなくなった

mapとflatmapのメモ

mapとflatmapのメモ.

map: 個数が変わらない。2個渡したら2個返ってくる
flatmap: streamに変換させて、それを合成する。戻ってくるのはstreamが1つ。flatmapはstreamを合成してくれる感じのいいやつ。

@RunWith(JUnit4.class)
public class SampleTest {
    @Test
    public void test() {

        // map
       Arrays.asList("a,b,c", "d,e")
            .stream()
            .map(e -> Arrays.stream(e.split(",")))
            .forEach(System.out::println);

        // flatmap
        Arrays.asList("a,b,c", "d,e")
            .stream()
            .flatMap(e -> Arrays.stream(e.split(",")))
            .forEach(System.out::println);
    }
}

出力結果は下記

# map
java.util.stream.ReferencePipeline$Head@18ce0030
java.util.stream.ReferencePipeline$Head@4445629

# flatmap
a
b
c
d
e

Spring webfluxでGETパラメータが長いときの ステータス413(Request Entity Too Large)の対処

Spring webfluxでGetパラメータが長いときに、HTTPステータス413(Request Entity Too Large)が発生して少々ハマった。

application.yamlとかで解決はできない感じで下記ソースにて解決

なお、SpringBoot@2.0.7

@Component
public class NettyServerCustomize implements WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {
    @Override
    public void customize(NettyReactiveWebServerFactory factory) {
        factory.addServerCustomizers(options -> {
            options.maxInitialLineLength(1024 * 1024);
        });
    }
}

この記事書いてて、SpringBootのバージョンあげないとなーって思いました

Mockitoでprivateメソッドのテストっぽいこと

ReflectionTestUtilsを使用する

fieldの場合

ReflectionTestUtils.setField を使う

public Sample SampleRepositoryImpl{
    private String parameter;
}


@Runwith(SpringRunner.class)
class SampleRepositoryImplTest(){

            @InjectMocks
            private SampleRepositoryImpl sampleRepository

            @Before
            public void setUp(){
                    ReflectionTestUtils.setField(sampleRepository,"parameter","local");
            }
}

上記のような感じで、差し替えができる。とても簡単。staticフィールドもできるもよう。

methodの場合

private methodをテストしたいときは、これで結果が取れるので、Assertiosでチェックしてやればいい。

ReflectionTestUtils.invokeMethod を使う

public Sample SampleRepositoryImpl{
        private Sring sampleMethod(){
             return "sample";
        }
}


@Runwith(SpringRunner.class)
class SampleRepositoryImplTest(){

            @InjectMocks
            private SampleRepositoryImpl sampleRepository

            @Test
            public void privateMethodTest(){
                    ReflectionTestUtils.invokeMethod(sampleRepository,"sampleMethod", null);
            }
}

staticメソッドもできるもよう。

まとめ

MockitoでprivateメソッドでテストするのはできなくてPowerMock使えって書いてあるけど、バージョンとか意識しないといけないの面倒だから、Mockitoでできないかなって調べたら、このくらいのことはできるので、わざわざライブラリ追加しなくていいかなって感じでした。

JavaのFunctionの合成はかなり便利

Java8から導入されたFunctionインターフェースはかなり便利な機能で多用している。特に関数合成が便利なので、ぜひ利用してほしい。

Functionインターフェース

define

Function<String, Integer> string2Integer = s -> Integer.valueOf(s);

Functionインターフェースではジェネリクスの1つめが引数、2つめが戻り値となっている
いつものJavaと比べるとちょっと変ですよね

use

s.apply("2");

これでOK。apply(引数)って感じで渡せばいいだけです。

他のパラメータも渡したい

これだと変数1個しか渡せなくて不便に感じる人もいるかとおもいます。変数宣言だと無理なのですが、メソッドとしてもFunctionインターフェース使えるのでこれを使うべしかなと思います。

define

Function<String, Integer> s(Integer param) {
    return s -> {
            return Integer.valueOf(s) + param;
        }
}

こんな感じになります。lambda式な感じになるので、違和感なく使えるかなと。メソッドの引数にパラメータを渡す感じです。

use

s(1).apply("2")

こんな感じで使います。 s(1) これでFunction<T,R>の戻り値を返却して、それをapplyする感じにしています。

合成

Functionは合成機能が標準であるので、これがとても便利です。
AやってBやってCやってみたいなのが、関数として組み合わせられて、状態依存もしにくいのでいいかなと。

define


Function<String, Integer> string2Integer = sVal -> Integer.valueOf(s); Function<String, Integer> sMethod(Integer param) { return s -> { return Integer.valueOf(s) + param; } }

先程の変数宣言とメソッド宣言を名前変えておきます。

use


sVal.andThen(sMethod(3)).apply("2")

こんな感じで使います。
andThenで関数と関数をくっつけます。(composeっていうのもあります)
実行順は andThencomposeで異なります。

andThenは A.andThen(B) だとすると A->Bの順
composeはA.compose(B)だとすると B->Aの順

で実行されます。

その他

関数合成ができてからとても便利になりました。
基本的に操作したい対象とパラメータはわけた方がよくて、パラメータ等はメソッドの引数で渡すのがいいと思います。
操作対象をFunctionインターフェースの方に渡すのがいいと思いました。

Intellijのgoogle-java-format plugin を外した.

ちょっと思うところがあって、google-java-format plugin を外した。
このプラグインがあると、結構強制力が強くて、indent4 にしたいときにできなかったり、import文を変えたかったりしたときにできなかったりとか・・・あったので、外しました…

ただそれだけ…

https://plugins.jetbrains.com/plugin/8527-google-java-format

Java8の@FunctionalInterfaceを使う

関数インターフェースのPredicateとかFunctionがあるのであんまり使わないけど、引数とか3つ以上のものはないのでつくる必要がある。

基本的に抽象メソッドを1つしかもっていなければOK。defaultメソッドはいくつもっていてもOKです。(ラムダ式のときに何を使うかわからなくなっちゃうからかなと)

@FunctionalInterface
interface SampleInterface {
      boolean filter(String a);
}

こんな感じでカンタンに作れる

使うときはこんな感じでカンタンに使える。

SampleInterfacle sampleFilter = s -> "a".equals(s);
sampleFilter.filter("a");

なんかもうカンタン過ぎますね

ちなみに引数3つは、こんな感じ

@FunctionalInterface
interface SampleInterface {
      boolean filter(String a, String b, String c);
}

SampleInterfacle sampleFilter = (s, t , u)  -> "a".equals(s);
sampleFilter.filter("a","b","c");

こんな感じで使える。なんかもうカンタン過ぎますね。みんなもっと使いましょう!