Kotlinのsplitを使って複数クエリのSQLファイルを実行する方法

前回に引き続きKotlinでsqlファイルを実行する実装を模索しています。
またしても学びごとがあったので記しておきます。

こちらの続きでSpringBootを利用しています。

https://qiita.com/Otofuke/items/4887cd084efe27710893

利用してるgradleの設定、Repositoryクラス諸々も、前回の記事と同様のものを使っています。

## 今回やりたいこと

例えば以下のようなSQLファイルを実行した結果をそれぞれ出力したい。

```sql:test.sql
select count(1) from staff; --1件ある
select count(1) from food; --3件ある
```

つまりファイル中の複数のcountクエリをそれぞれ実行して、結果を取得したい。

## やったこと

- splitの挙動について
- 正規表現で区切り文字を含む方法

これらを踏まえてやりたいことを叶えていきます。

### splitの挙動について

;区切りで文字列を取得する場合、Java同様こんなイメージをしていました。

```Kotlin
val list = "aaa;bbb;ccc;".split(";")
println(list)
println(list.size)
```

出力結果はこうなりました。

```shell
[aaa, bbb, ccc, ]
4
```

一番後ろの;で区切った時の空文字列分の値が入っちゃってますね。

#### Javaでは

https://docs.oracle.com/javase/jp/15/docs/api/java.base/java/lang/String.html#split(java.lang.String,int)

Javaでは第二引数に0を設定すると末尾の空文字列が廃棄されます。
しかし、、

#### Kotlinでは

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/split.html

> when the limit is not specified or specified as 0, this function doesn't drop trailing empty strings from the result.

末尾の空文字列は除去しませんよとご丁寧に書いてありました。

#### 結論：JavaとKotlinのsplitの挙動は違う

ので、手動で空文字列を消すことにしました。
以下末尾の空文字列を根こそぎ消すというもの。

```Kotlin
    val list = "aaa;bbb;ccc".split(";")
    println(list.dropLastWhile { it.isBlank() })
    println(list.size)
```

出力結果

```shell
[aaa, bbb, ccc]
3
```

こんなに簡単に書けるなんて。Kotlinすばら。

### 正規表現で区切り文字を含む方法

今回はSQLの区切り文字である;を含めたかったので
以下のような正規表現を使うことにしました。

```Kotlin
Regex("(?<=;)")
```

直前に;がある文字列を表す正規表現です。

Regexにもsplit関数が用意されているようなのでこれを使います。

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/-regex/split.html

Stringのsplit同様、第二引数で空文字列を除去することはできませんでした。

```Kotlin
val reg = Regex("(?<=;)")
print(reg.split("aaa;bbb;ccc;", 0))
```

出力結果

```shell
[aaa;, bbb;, ccc;, ]
```

## まとめて適用して実行する

以下のようにServiceクラスにこれらを実装していきます。

```Kotlin:SakuraService.kt
@Service
class SakuraService {

    /**
       SQLファイル内のクエリを実行する
       @param ファイルパス
       @return SQLクエリ文字列のリスト
     */
    fun getSqlQueryList(filepath:String):List<String>{
        val fileString = Files.readString(Paths.get(filepath))

        //区切り文字を含む文字列を取得するための正規表現
        val reg = Regex("(?<=;)")
        
        //Kotlinのsplitは末尾空白列除去オプションがないため手動で削除
        return  reg.split(fileString).dropLastWhile { it.isBlank() }
    }
}
```

実行するApplicatioクラス、main関数はこんな感じです。

```Kotlin:SakuraApplication.kt
package com.example.sakura

import com.example.sakura.repository.SakuraRepository
import com.example.sakura.service.SakuraService
import org.springframework.boot.CommandLineRunner
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.jdbc.core.JdbcTemplate
import java.io.File

@SpringBootApplication
class SakuraApplication(val jdbcTemplate: JdbcTemplate) : CommandLineRunner {
    override fun run(vararg args: String?) {
        val service = SakuraService()
        val repository = SakuraRepository(jdbcTemplate)

        //sqlファイルからクエリの一覧を抽出する
        val sqlList = service.getSqlQueryList("src/main/resources/sql/test.sql")

        for (sql in sqlList) {
            println(sql)
            //sql実行結果
            println(repository.count(sql))
        }
    }
}

fun main(args: Array<out String?>) {
	SpringApplication.run(SakuraApplication::class.java, *args)
}

```

JdbcTemplateについては先の記事に記載してますので参照ください。

https://qiita.com/Otofuke/items/4887cd084efe27710893

実行した出力結果がこちら

```shell
select count(1) from staff;
1
select count(1) from food;
3
```

出来上がり。

### 最後に

区切り文字の;がなくてもクエリごとに文字列切ってれば実行できたりします。
SQL実行時に区切り文字あった方が安心だよね。っていう気持ちでくっつけてます。