【Corda】Stateの検索方法〜VaultQueryの使用方法と、JDBCを利用したSQLの発行方法について〜

どーも、のぶこふです。
最近、Cordaのノード起動する度に、PCがウィンウィンいうので「PC頑張れっ！みんながんばれ！むしろ、ガンガンいこうぜ！！」と心の中で叫んでいます。

今回は、引き続きVaultQueryの使用方法と、JDBCを利用したSQLの発行方法について書きます。
VaultQueryの使用方法については、[前々回の記事（[Corda]VaultQueryを使って、Vaultを検索したり、H2DBを使ったり）](https://qiita.com/nobkovskii/items/7fa7b624055bd0b5ff6c)と内容が重複しますので、その時のソースが手元にある場合は、そちらをお使いください。
（SchemaとStateは流用しています）

## 今回の記事 is 何？
* VaultQueryの使用方法がわかる
* JDBCを利用したSQLの発行方法がわかる
* するとどうなる？？？
    * →TransactionのInputとして利用できる！！

## はじめに
お約束のCordaテンプレートを使用します。
なお、今回使用PCはMacです。

## 環境準備
* 適当にディレクトリを作成します。（省略可）
* テンプレートをgit clone します。
* IDEを起動します。

```terminal:お約束のコマンドたち
$ mkdir vaultTest
$ cd vaultTest/
$ git clone https://github.com/corda/cordapp-template-kotlin.git
Cloning into 'cordapp-template-kotlin'...
remote: Enumerating objects: 39, done.
remote: Counting objects: 100% (39/39), done.
remote: Compressing objects: 100% (27/27), done.
remote: Total 3480 (delta 7), reused 25 (delta 0), pack-reused 3441
Receiving objects: 100% (3480/3480), 2.21 MiB | 2.37 MiB/s, done.
Resolving deltas: 100% (1259/1259), done.
```

<img width="508" alt="スクリーンショット 2019-09-04 8.51.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306608/62b93f34-6123-a4b2-525c-25999d4478dd.png">


## Schemaの作成
* DB(インメモリ=H2DB)に独自Stateの情報を保存出来るように、Schemaを作成します。
* contracts/src/main/kotlin/com/templateにschemasディレクトリを作成し、その下にTemplateSchema.ktを作成します。

<img width="316" alt="スクリーンショット 2019-09-04 8.56.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306608/afcb35ab-48ad-7eda-abbd-b7cb266d0f47.png">

```kotlin:TemplateSchema.kt（コピペでOKです！！）
package com.template.schemas

import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentState
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.Table

object TemplateSchema
object TemplateSchemaV1 : MappedSchema(
        schemaFamily = TemplateSchema.javaClass,
        version = 1,
        mappedTypes = listOf(TemplateSchemaV1.PersistentTemplate::class.java)
) {
    @Entity
    @Table(name = "template_state")
    class PersistentTemplate(
            @Column(name = "id")
            var id: Int,
            @Column(name = "data")
            var data: String,
            @Column(name = "counterParty")
            var counterParty: String,
            @Column(name = "issuer")
            var issuer: String
    ) : PersistentState() {
        constructor() : this(0,"","","")
    }
}

```

## Stateの修正
* 検索が出来るようにQueryableStateの実装と、Stateを一意に識別出来るようにLinearStateを実装します。

```kotlin:TemplateState.kt（コピペでOKです！！）
package com.template.states

import com.template.contracts.TemplateContract
import com.template.schemas.TemplateSchemaV1
import net.corda.core.contracts.BelongsToContract
import net.corda.core.contracts.LinearState
import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentState
import net.corda.core.schemas.QueryableState

// *********
// * State *
// *********
@BelongsToContract(TemplateContract::class)
data class TemplateState(
        val issuer :Party,
        val counterParty: Party,
        val id : Int,
        val data: String,
        override val linearId: UniqueIdentifier = UniqueIdentifier(),
        override val participants: List<AbstractParty> = listOf(issuer,counterParty)
) : LinearState, QueryableState {
    override fun generateMappedObject(schema: MappedSchema): PersistentState {
        return when(schema){
            is TemplateSchemaV1 -> TemplateSchemaV1.PersistentTemplate(this.id,this.data,counterParty.name.toString(),issuer.name.toString())
            else -> throw IllegalArgumentException("Unrecognised schema $schema")
        }
    }

    override fun supportedSchemas(): Iterable<MappedSchema> = listOf(TemplateSchemaV1)
}
```

## Flowの修正
### ▼VaultQueryの実装
* Initiatorの引数を変更します。
    * この引数を使って、Outputを作成します。
    * `: FlowLogic<Unit>()`は、callの戻り値を指定しています。
        * callの戻り値がUnitなら上記のように、Stringなら`: FlowLogic<String>()`のように記述します。ここに関しては制限は無いので、自由に記述することが可能です。
        * callの結果を他のシステムに連携する時などは、戻り値は必須ですね。


```kotlin:Flow.kt
class Initiator(private val counterParty: Party, private val id: Int, private val data: String) : FlowLogic<Unit>() {
```

* Notaryの取得と、VaultQueryの設定を行います。
    * Notaryは`serviceHub`の`networkMapCache`から取得します。
    * `VaultQueryCriteria`に`Vault.StateStatus.UNCONSUMED`と指定することで、ステータスが未消費のStateに絞り込みます。
        * `Vault.StateStatus.CONSUMED`なら消費済み
        * `Vault.StateStatus.ALL`なら全て
        * `Vault.StateStatus.UNCONSUMED`か、未指定の場合は、未消費のStateが対象になります
        * ただし、以下の**builderで検索条件の絞り込みを行うと、デフォルトの`Vault.StateStatus.UNCONSUMED`に上書きされてしまう**ようです。
    * `val condition1 = TemplateSchemaV1.PersistentTemplate::id.equal(id)`で使用しているのは`net.corda.core.node.services.vault.builder`の`equal()`です。`kotlin.any`で提供している`equals()`では無いので注意が必要です。 
        * たまにサジェストをミスってエラーが発生して「？？？」となる

```kotlin:Flow.kt
    @Suspendable
    override fun call() {
        // デフォルトのNotaryを指定
        val notary = serviceHub.networkMapCache.notaryIdentities[0]

        // 未使用のState、かつIDが等しいStateを検索
        val generalCriteria = QueryCriteria.VaultQueryCriteria(Vault.StateStatus.UNCONSUMED)
        val states = builder {
            val condition1 = TemplateSchemaV1.PersistentTemplate::id.equal(id)
            val q1 = QueryCriteria.VaultCustomQueryCriteria(condition1)
            val criteria = generalCriteria.and(q1)
            serviceHub.vaultService.queryBy<TemplateState>(criteria).states
        }
```

* 続いてcallにOutput、Command、Transactionを作成します。
    * 引数を使用しOutput用のStateを作成します。
    * CommandはデフォルトのActionを使用します。
        * 何もしていない空のコントラクトです
    * Transactionには、作成したOutputとCommandを指定します。
        * inputを指定する場合は`.addInputState(StateAndRef<*>)`を指定します。
        * `StateAndRef<*>`は、ざっくりと`queryBy()`の戻り値と認識しておけばOKです。
        * `TransactionBuilder`の`add~`は順序に指定はないので、先にInputをAddするなどもOKです。
    * 最後の`txBuilder.verify(serviceHub)`で、Commandが正しく設定されているか？などTransactionのチェックを行っています。

```kotlin:Flow.kt
        // Create Output
        val output = TemplateState(issuer = ourIdentity,counterParty = counterParty,id= this.id,data = this.data)

        // Create command
        val cmd =
            Command(TemplateContract.Commands.Action(), listOf(ourIdentity.owningKey, counterParty.owningKey))

        // Create Transaction
        val txBuilder = TransactionBuilder(notary)
            .addOutputState(output)
            .addCommand(cmd)

        // Add Input
        if (states.isNotEmpty()){
            txBuilder.addInputState(states.first())
        }

        // Verify Transaction
        txBuilder.verify(serviceHub)
```

* 最後にTransactionに自身の署名、他者の署名、Notaryの署名＆二重支払いチェックを行い、Transactionを確定させます。
    * `signInitialTransaction(txBuilder)`で自身の署名を取得します。
    * `initiateFlow(counterParty)`で他者のセッションを起動し、`subFlow`として`CollectSignaturesFlow(signedTx, setOf(counterPartySession))`で他者の署名を取得します。
    * `FinalityFlow(fullySignedTx, setOf(counterPartySession))`でNotaryの署名＆二重支払いチェックを行い、Transactionを確定させています。


```kotlin
        // Signing Transaction
        val signedTx = serviceHub.signInitialTransaction(txBuilder)

        // Gathering Signs
        val counterPartySession = initiateFlow(counterParty)
        val fullySignedTx = subFlow(CollectSignaturesFlow(signedTx, setOf(counterPartySession)))

        // Finalize Transaction
        subFlow(FinalityFlow(fullySignedTx, setOf(counterPartySession)))
    }
```

* おっと、Responderの設定も必要でした。
    * `Responder`は`Initiator`の`CollectSignaturesFlow`を受けて実行されます。
    * 未実装の場合は`net.corda.core.flows.UnexpectedFlowEndException: Tried to access ended session SessionId(toLong=XXX) with empty buffer`のようなエラーが発生します。

```kotlin:Flow.kt
@InitiatedBy(Initiator::class)
class Responder(val counterPartySession: FlowSession) : FlowLogic<Unit>() {
    @Suspendable
    override fun call() {
        val signTransactionFlow = object : SignTransactionFlow(counterPartySession) {
            override fun checkTransaction(stx: SignedTransaction) = requireThat {}
        }
        val txId = subFlow(signTransactionFlow).id
        subFlow(ReceiveFinalityFlow(counterPartySession, expectedTxId = txId))
    }
}
```


* Flow.ktの全量は以下になります

<details><summary>Flow.ktの全量（長いので折りたたみ）</summary><div>

```kotlin:Flow.kt（コピペOKです！！）
package com.template.flows

import co.paralleluniverse.fibers.Suspendable
import com.template.contracts.TemplateContract
import com.template.schemas.TemplateSchemaV1
import com.template.states.TemplateState
import net.corda.core.contracts.Command
import net.corda.core.contracts.requireThat
import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.node.services.Vault
import net.corda.core.node.services.queryBy
import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.node.services.vault.builder
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker

// *********
// * Flows *
// *********
@InitiatingFlow
@StartableByRPC
class Initiator(private val counterParty: Party, private val id: Int, private val data: String) : FlowLogic<Unit>() {
    override val progressTracker = ProgressTracker()

    @Suspendable
    override fun call() {
        // デフォルトのNotaryを指定
        val notary = serviceHub.networkMapCache.notaryIdentities[0]
        id.equals(id)

        val generalCriteria = QueryCriteria.VaultQueryCriteria(Vault.StateStatus.UNCONSUMED)
        val states = builder {
            val condition1 = TemplateSchemaV1.PersistentTemplate::id.equal(id)
            val q1 = QueryCriteria.VaultCustomQueryCriteria(condition1)
            val criteria = generalCriteria.and(q1)
            serviceHub.vaultService.queryBy<TemplateState>(criteria).states
        }

        // Create Output
        val output = TemplateState(issuer = ourIdentity,counterParty = counterParty,id= this.id,data = this.data)

        // Create command
        val cmd =
                Command(TemplateContract.Commands.Action(), listOf(ourIdentity.owningKey, counterParty.owningKey))

        // Create Transaction
        val txBuilder = TransactionBuilder(notary)
                .addOutputState(output)
                .addCommand(cmd)

        // Add Input
        if (states.isNotEmpty()){
            txBuilder.addInputState(states.first())
        }

        txBuilder.verify(serviceHub)

        // Signing Transaction
        val signedTx = serviceHub.signInitialTransaction(txBuilder)

        // Gathering Signs
        val counterPartySession = initiateFlow(counterParty)
        val fullySignedTx = subFlow(CollectSignaturesFlow(signedTx, setOf(counterPartySession)))

        // Finalize Transaction
        subFlow(FinalityFlow(fullySignedTx, setOf(counterPartySession)))
    }
}

@InitiatedBy(Initiator::class)
class Responder(val counterPartySession: FlowSession) : FlowLogic<Unit>() {
    @Suspendable
    override fun call() {
        val signTransactionFlow = object : SignTransactionFlow(counterPartySession) {
            override fun checkTransaction(stx: SignedTransaction) = requireThat {}
        }
        val txId = subFlow(signTransactionFlow).id
        subFlow(ReceiveFinalityFlow(counterPartySession, expectedTxId = txId))
    }
}

```
</div></details>


### ▼試しに動かす
* これで、VaultQueryが使える準備が出来ました。実際に動かして試してみましょう。

```terminal:ディレクトリを移動して、いつものコマンド（ビルド→ノード起動）を実行する
$ cd cordapp-template-kotlin/
$ ./gradlew deployNodes
$ ./build/nodes/runnodes
```

* ノードの起動まで終わったら、PartyAのノードで実行してみます。
    * Stateに登録されていることがわかります。

```terminal:PartyAのノードでInitiatorのFlowを実行する（1回目）
>>> start Initiator counterParty: "PartyB" , id: 1 , data: "hoge" 

 ✅   Starting
          Requesting signature by notary service
              Requesting signature by Notary service
              Validating response from Notary service
     ✅   Broadcasting transaction to participants
➡️   Done
Flow completed with result: kotlin.Unit

>>> run vaultQuery contractStateType: com.template.states.TemplateState
states:
- state:
    data: !<com.template.states.TemplateState>
      issuer: "O=PartyA, L=London, C=GB"
      counterParty: "O=PartyB, L=New York, C=US"
      id: 1
      data: "hoge"
      linearId:
        externalId: null
        id: "62bec7c3-7b64-4fbf-8d9e-3845a0142a43"
      participants:
      - "O=PartyA, L=London, C=GB"
      - "O=PartyB, L=New York, C=US"
    contract: "com.template.contracts.TemplateContract"
    notary: "O=Notary, L=London, C=GB"
    encumbrance: null
    constraint: !<net.corda.core.contracts.SignatureAttachmentConstraint>
      key: "aSq9DsNNvGhYxYyqA9wd2eduEAZ5AXWgJTbTEw3G5d2maAq8vtLE4kZHgCs5jcB1N31cx1hpsLeqG2ngSysVHqcXhbNts6SkRWDaV7xNcr6MtcbufGUchxredBb6"
  ref:
    txhash: "C6D1F7540749A88E69F7A2D058C2E15B3D9BBE71E624A58F325FE073E64042B9"
    index: 0
statesMetadata:
- ref:
    txhash: "C6D1F7540749A88E69F7A2D058C2E15B3D9BBE71E624A58F325FE073E64042B9"
    index: 0
  contractStateClassName: "com.template.states.TemplateState"
  recordedTime: "2019-09-04T01:49:26.643Z"
  consumedTime: null
  status: "UNCONSUMED"
  notary: "O=Notary, L=London, C=GB"
  lockId: null
  lockUpdateTime: null
  relevancyStatus: "RELEVANT"
  constraintInfo:
    constraint:
      key: "aSq9DsNNvGhYxYyqA9wd2eduEAZ5AXWgJTbTEw3G5d2maAq8vtLE4kZHgCs5jcB1N31cx1hpsLeqG2ngSysVHqcXhbNts6SkRWDaV7xNcr6MtcbufGUchxredBb6"
totalStatesAvailable: -1
stateTypes: "UNCONSUMED"
otherResults: []
```

* 少し内容を変えて、実行してみます。
    * 引数のdataをhogeからFugaに変更しています。
    * こちらも無事実行されていますが、実行結果を見てお気づきの方もいらっしゃるかと思います。

```terminal:PartyAのノードでInitiatorのFlowを実行する（2回目）
>>> start Initiator counterParty: "PartyB" , id: 1 , data: "Fuga"      

 ✅   Starting
     ✅   Requesting signature by notary service
         ✅   Requesting signature by Notary service
         ✅   Validating response from Notary service
     ✅   Broadcasting transaction to participants
➡️   Done
Flow completed with result: kotlin.Unit

>>> run vaultQuery contractStateType: com.template.states.TemplateState
states:
- state:
    data: !<com.template.states.TemplateState>
      issuer: "O=PartyA, L=London, C=GB"
      counterParty: "O=PartyB, L=New York, C=US"
      id: 1
      data: "Fuga"
      linearId:
        externalId: null
        id: "0332a33a-0c0d-4708-80e4-307b4322c68c"
      participants:
      - "O=PartyA, L=London, C=GB"
      - "O=PartyB, L=New York, C=US"
    contract: "com.template.contracts.TemplateContract"
    notary: "O=Notary, L=London, C=GB"
    encumbrance: null
    constraint: !<net.corda.core.contracts.SignatureAttachmentConstraint>
      key: "aSq9DsNNvGhYxYyqA9wd2eduEAZ5AXWgJTbTEw3G5d2maAq8vtLE4kZHgCs5jcB1N31cx1hpsLeqG2ngSysVHqcXhbNts6SkRWDaV7xNcr6MtcbufGUchxredBb6"
  ref:
    txhash: "758D3F22DE3E48409E9E29113AEB49EB5B5037BDDEE842AF1C24416317F426DC"
    index: 0
statesMetadata:
- ref:
    txhash: "758D3F22DE3E48409E9E29113AEB49EB5B5037BDDEE842AF1C24416317F426DC"
    index: 0
  contractStateClassName: "com.template.states.TemplateState"
  recordedTime: "2019-09-04T01:52:09.295Z"
  consumedTime: null
  status: "UNCONSUMED"
  notary: "O=Notary, L=London, C=GB"
  lockId: null
  lockUpdateTime: null
  relevancyStatus: "RELEVANT"
  constraintInfo:
    constraint:
      key: "aSq9DsNNvGhYxYyqA9wd2eduEAZ5AXWgJTbTEw3G5d2maAq8vtLE4kZHgCs5jcB1N31cx1hpsLeqG2ngSysVHqcXhbNts6SkRWDaV7xNcr6MtcbufGUchxredBb6"
totalStatesAvailable: -1
stateTypes: "UNCONSUMED"
otherResults: []
```

* もう一度、内容を変更して実行してみます。
    * idを1から2に、dataをFugaからBarに変更しています
* `run vaultQuery`の結果から見てわかる通り、件数が増えましたね。
    * `run vaultQuery`で表示しているのは、未消費のStateだけです。
    * 2回目に実行した時は、1回目と同じIDを指定していたのでStateをInputとして消費しています。そのため、2回目に確認したときに、1回目の結果が表示されなかったのです。
    * しかし、3回目に実行した時は、別のIDを指定していたので、Input無しでTransactionが発行されました。

```terminal:PartyAのノードでInitiatorのFlowを実行する（3回目）
>>> start Initiator counterParty: "PartyB" , id: 2 , data: "Bar"      
 ✅   Starting
          Requesting signature by notary service
              Requesting signature by Notary service
              Validating response from Notary service
     ✅   Broadcasting transaction to participants
➡️   Done
Flow completed with result: kotlin.Unit

>>> run vaultQuery contractStateType: com.template.states.TemplateState
states:
- state:
    data: !<com.template.states.TemplateState>
      issuer: "O=PartyA, L=London, C=GB"
      counterParty: "O=PartyB, L=New York, C=US"
      id: 1
      data: "Fuga"
      linearId:
        externalId: null
        id: "0332a33a-0c0d-4708-80e4-307b4322c68c"
      participants:
      - "O=PartyA, L=London, C=GB"
      - "O=PartyB, L=New York, C=US"
    contract: "com.template.contracts.TemplateContract"
    notary: "O=Notary, L=London, C=GB"
    encumbrance: null
    constraint: !<net.corda.core.contracts.SignatureAttachmentConstraint>
      key: "aSq9DsNNvGhYxYyqA9wd2eduEAZ5AXWgJTbTEw3G5d2maAq8vtLE4kZHgCs5jcB1N31cx1hpsLeqG2ngSysVHqcXhbNts6SkRWDaV7xNcr6MtcbufGUchxredBb6"
  ref:
    txhash: "758D3F22DE3E48409E9E29113AEB49EB5B5037BDDEE842AF1C24416317F426DC"
    index: 0
- state:
    data: !<com.template.states.TemplateState>
      issuer: "O=PartyA, L=London, C=GB"
      counterParty: "O=PartyB, L=New York, C=US"
      id: 2
      data: "Bar"
      linearId:
        externalId: null
        id: "38efca42-3fe1-4c6d-be74-00404715f77e"
      participants:
      - "O=PartyA, L=London, C=GB"
      - "O=PartyB, L=New York, C=US"
    contract: "com.template.contracts.TemplateContract"
    notary: "O=Notary, L=London, C=GB"
    encumbrance: null
    constraint: !<net.corda.core.contracts.SignatureAttachmentConstraint>
      key: "aSq9DsNNvGhYxYyqA9wd2eduEAZ5AXWgJTbTEw3G5d2maAq8vtLE4kZHgCs5jcB1N31cx1hpsLeqG2ngSysVHqcXhbNts6SkRWDaV7xNcr6MtcbufGUchxredBb6"
  ref:
    txhash: "36218C75A486E5A9889F73136939E6121604DD1660A2A732AD9DDC76D9E0F491"
    index: 0
statesMetadata:
- ref:
    txhash: "758D3F22DE3E48409E9E29113AEB49EB5B5037BDDEE842AF1C24416317F426DC"
    index: 0
  contractStateClassName: "com.template.states.TemplateState"
  recordedTime: "2019-09-04T01:52:09.295Z"
  consumedTime: null
  status: "UNCONSUMED"
  notary: "O=Notary, L=London, C=GB"
  lockId: null
  lockUpdateTime: null
  relevancyStatus: "RELEVANT"
  constraintInfo:
    constraint:
      key: "aSq9DsNNvGhYxYyqA9wd2eduEAZ5AXWgJTbTEw3G5d2maAq8vtLE4kZHgCs5jcB1N31cx1hpsLeqG2ngSysVHqcXhbNts6SkRWDaV7xNcr6MtcbufGUchxredBb6"
- ref:
    txhash: "36218C75A486E5A9889F73136939E6121604DD1660A2A732AD9DDC76D9E0F491"
    index: 0
  contractStateClassName: "com.template.states.TemplateState"
  recordedTime: "2019-09-04T01:57:28.737Z"
  consumedTime: null
  status: "UNCONSUMED"
  notary: "O=Notary, L=London, C=GB"
  lockId: null
  lockUpdateTime: null
  relevancyStatus: "RELEVANT"
  constraintInfo:
    constraint:
      key: "aSq9DsNNvGhYxYyqA9wd2eduEAZ5AXWgJTbTEw3G5d2maAq8vtLE4kZHgCs5jcB1N31cx1hpsLeqG2ngSysVHqcXhbNts6SkRWDaV7xNcr6MtcbufGUchxredBb6"
totalStatesAvailable: -1
stateTypes: "UNCONSUMED"
otherResults: []
```

### ▼JDBCの実装
* 公式ドキュメントの[JDBC session](https://docs.corda.net/api-persistence.html?highlight=sql#jdbc-session)に詳細が記載されています。
* NodeのDatabaseのテーブル定義は[Node database tables](https://docs.corda.net/node-database.html?highlight=database#node-database-tables)に記載されています。
    * Schemaを作成すると、ここに追加されていきます。
* この内容を元に、Flow.ktを拡張していきます。
    * せっかくなので、NodeのTransactionIDを取得してみます。
    * 下記をFlow.ktの末尾に追加します。

```kotlin:Flow.ktに追加
object CustomVaultQuery {
    @CordaService
    class Service(val services: AppServiceHub) : SingletonSerializeAsToken() {
        private companion object {
            private val log = contextLogger()
        }

        // 自ノードの全StateからTransactionIDを取得する
        fun getTxIDs(): List<String> {
            val nativeQuery = """
                select
                    TRANSACTION_ID
                from
                    VAULT_STATES
                order by
                    RECORDED_TIMESTAMP
            """
            log.info("SQL to execute: $nativeQuery")
            val session = services.jdbcSession()
            return session.prepareStatement(nativeQuery).use { prepStatement ->
                prepStatement.executeQuery().use { rs ->
                    val tx_ids: MutableList<String> = mutableListOf()
                    while (rs.next()) {
                        val tx_id = rs.getString("TRANSACTION_ID")
                        tx_ids.add(tx_id)
                    }
                    tx_ids
                }
            }
        }
    }
}
```

* 次に、Flow.ktのcallに、このCordaServiceを使用するコードを追記します。
    * 特にこのTransactionIDを使うわけでもないので、標準出力するにとどめておきます。

```kotlin:Flow.ktのcallに記述
    @Suspendable
    override fun call() {
        // デフォルトのNotaryを指定
        val notary = serviceHub.networkMapCache.notaryIdentities[0]
        id.equals(id)

        val generalCriteria = QueryCriteria.VaultQueryCriteria(Vault.StateStatus.UNCONSUMED)
        val states = builder {
            val condition1 = TemplateSchemaV1.PersistentTemplate::id.equal(id)
            val q1 = QueryCriteria.VaultCustomQueryCriteria(condition1)
            val criteria = generalCriteria.and(q1)
            serviceHub.vaultService.queryBy<TemplateState>(criteria).states
        }

        // CordaService
        val customVaultQueryService = serviceHub.cordaService(CustomVaultQuery.Service::class.java)
        val txIDs = customVaultQueryService.getTxIDs()
        for (txID in txIDs){
            println(txID)
        }
```

### ▼試しに動かす
* これで、JDBCも使えるようになりました。実際に動かして試してみましょう。
    * ノードが起動したままであれば、ターミナルを閉じるか、byeコマンドを実行して、ノードを停止させなす。

```terminal:いつものコマンド（ビルド→ノード起動）を実行する
$ ./gradlew deployNodes
$ ./build/nodes/runnodes
```

* ノードの起動まで終わったら、PartyAのノードで実行してみます。
    * おや、何も表示されませんね・・・

```terminal:PartyAのノードでInitiatorのFlowを実行する（1回目）
>>> start Initiator counterParty: "PartyB" , id: 3 , data: "nobkov" 

 ✅   Starting
          Requesting signature by notary service
              Requesting signature by Notary service
              Validating response from Notary service
     ✅   Broadcasting transaction to participants
➡️   Done
Flow completed with result: kotlin.Unit
```

* もう一度実行してみます。
    * それらしき文字列が表示されていますね。
    * 1回目はTransactionの発行前に検索を行っていたので、IDが無いのは当然でした。
* `run vaultQuery`を見てみると、1回目のTransactionIDと合致していることがわかります。
    * `ref`の`txhash`がTransactionIDです。

```terminal:PartyAのノードでInitiatorのFlowを実行する（2回目）
>>> start Initiator counterParty: "PartyB" , id: 4 , data: "test"   
D52F9B1561A97BC68E6134EA20C8AC11D768C8E91E6F301DF76A18E177B75F3D

 ✅   Starting
          Requesting signature by notary service
              Requesting signature by Notary service
              Validating response from Notary service
     ✅   Broadcasting transaction to participants
➡️   Done
Flow completed with result: kotlin.Unit

>>> run vaultQuery contractStateType: com.template.states.TemplateState
states:
- state:
    data: !<com.template.states.TemplateState>
      issuer: "O=PartyA, L=London, C=GB"
      counterParty: "O=PartyB, L=New York, C=US"
      id: 3
      data: "nobkov"
      linearId:
        externalId: null
        id: "e5cd54ad-c992-43e9-87b3-49918ecbcce7"
      participants:
      - "O=PartyA, L=London, C=GB"
      - "O=PartyB, L=New York, C=US"
    contract: "com.template.contracts.TemplateContract"
    notary: "O=Notary, L=London, C=GB"
    encumbrance: null
    constraint: !<net.corda.core.contracts.SignatureAttachmentConstraint>
      key: "aSq9DsNNvGhYxYyqA9wd2eduEAZ5AXWgJTbTEw3G5d2maAq8vtLE4kZHgCs5jcB1N31cx1hpsLeqG2ngSysVHqcXhbNts6SkRWDaV7xNcr6MtcbufGUchxredBb6"
  ref:
    txhash: "D52F9B1561A97BC68E6134EA20C8AC11D768C8E91E6F301DF76A18E177B75F3D"
    index: 0
- state:
    data: !<com.template.states.TemplateState>
      issuer: "O=PartyA, L=London, C=GB"
      counterParty: "O=PartyB, L=New York, C=US"
      id: 4
      data: "test"
      linearId:
        externalId: null
        id: "d0037f1b-f085-4ce1-8022-9d89041c699c"
      participants:
      - "O=PartyA, L=London, C=GB"
      - "O=PartyB, L=New York, C=US"
    contract: "com.template.contracts.TemplateContract"
    notary: "O=Notary, L=London, C=GB"
    encumbrance: null
    constraint: !<net.corda.core.contracts.SignatureAttachmentConstraint>
      key: "aSq9DsNNvGhYxYyqA9wd2eduEAZ5AXWgJTbTEw3G5d2maAq8vtLE4kZHgCs5jcB1N31cx1hpsLeqG2ngSysVHqcXhbNts6SkRWDaV7xNcr6MtcbufGUchxredBb6"
  ref:
    txhash: "529B88050662CE4BD8216BCA4674FB1652D7312645CD2D1BC797DC78ABB33ED1"
    index: 0
statesMetadata:
- ref:
    txhash: "D52F9B1561A97BC68E6134EA20C8AC11D768C8E91E6F301DF76A18E177B75F3D"
    index: 0
  contractStateClassName: "com.template.states.TemplateState"
  recordedTime: "2019-09-04T02:45:05.994Z"
  consumedTime: null
  status: "UNCONSUMED"
  notary: "O=Notary, L=London, C=GB"
  lockId: null
  lockUpdateTime: null
  relevancyStatus: "RELEVANT"
  constraintInfo:
    constraint:
      key: "aSq9DsNNvGhYxYyqA9wd2eduEAZ5AXWgJTbTEw3G5d2maAq8vtLE4kZHgCs5jcB1N31cx1hpsLeqG2ngSysVHqcXhbNts6SkRWDaV7xNcr6MtcbufGUchxredBb6"
- ref:
    txhash: "529B88050662CE4BD8216BCA4674FB1652D7312645CD2D1BC797DC78ABB33ED1"
    index: 0
  contractStateClassName: "com.template.states.TemplateState"
  recordedTime: "2019-09-04T02:48:50.456Z"
  consumedTime: null
  status: "UNCONSUMED"
  notary: "O=Notary, L=London, C=GB"
  lockId: null
  lockUpdateTime: null
  relevancyStatus: "RELEVANT"
  constraintInfo:
    constraint:
      key: "aSq9DsNNvGhYxYyqA9wd2eduEAZ5AXWgJTbTEw3G5d2maAq8vtLE4kZHgCs5jcB1N31cx1hpsLeqG2ngSysVHqcXhbNts6SkRWDaV7xNcr6MtcbufGUchxredBb6"
totalStatesAvailable: -1
stateTypes: "UNCONSUMED"
otherResults: []
```

## おわりに
今回は、Corda実装には欠かせないStateの検索方法についての記事でした。
特にJDBCでの検索方法は、SQLを記述できることもあり、柔軟な記載ができることや、任意のテーブルに対して射影ができるのが強みです。
＃そういえば、ここでUPDATEとか使ったらどうなるんだろ・・・

次回は「Transactionの中身を見てみよう」とか「Transactionを遡ってみよう」とか、Transaction周りについて記事が書ければと思います。

今回はここまでです。
ありがとうございました。