GridDBとSpring Bootを使ってJava Web APIを作成する

GridDBは様々なプログラミング言語で利用することができますが、Javaは唯一のネイティブ・コネクタであるため、GridDBとすぐに連携することができます。ネイティブ・コネクタなので、JavaのSpring Bootを使ってCRUD APIを書く方法を記事にすることにしました。

Spring Bootライブラリを使えば、REST Interfaceコマンドを受け付けるサーバを簡単に立ち上げることができます。そこで今回は、ユーザがGridDBデータベースからCreate、Read、Update、DeleteできるJavaコードを書いてみましょう。

RESTfulバッファを通してデータベースにアクセスすることには多くの利点がありますが、最大の利点はセキュリティという形でもたらされます。おそらく想像がつくと思いますが、ユーザーが本番データベースに直接アクセスすることは常にリスクが伴います。

Spring BootでRESTful APIを作成することで、ユーザーへの公開を制限し、必要なものだけを読み取り、書き込み、更新、削除を許可することができます。 その仕組みは、Javaで関数を作成し、あらかじめ設定されたクエリー・ステートメントを持ち、ユーザーが生成したパラメータを1つか2つ取り込んでデータベースとやり取りするというものです。 ユーザーに1つか2つのパラメータしか変更させないようにすることで、機密情報を含む可能性のあるGridDBコンテナへのアクセスを直接ブロックすることができます。

APIエンドポイントを使ってデータベースとやり取りする大前提は以下の通りです。 ユーザーがHTTPリクエストを行うと、使用されるメソッド(GET、POST、PUT、DELETEなど)に応じて、タスクを達成する異なるJava関数が呼び出されます。

そこで、この記事では、このプロジェクトに含まれるソース・コードについて説明します。このソース・コードでは、ユーザーが安全なバッファを通じてGridDBとやり取りできるようにするAPIエンドポイントを作成します。このソースコードでは、GridDBの基本的なタスクを効率的な方法で達成できるJavaコードの書き方も紹介します。

前提条件とフォロー

このブログをフォローしたい場合は、ここで公開されているGitHubのレポをクローンすることができます。

また、いくつかの予備知識も必要です。

  1. GridDBのインストールと実行
  2. Maven

そして、次のコマンド$ mvn spring-boot:runを使ってプロジェクトを実行できます。すべてが上手くいけば、ターミナルでSpring Bootのインスタンスがフォアグラウンドで実行され、ポート8081でサーバーのIPアドレスにアクセスできるようになります。例えば: http://localhost:8081.

デバイスクラス

重要なGridDBクラスに入る前に、まずDeviceクラスから説明しましょう。

このクラスは、この記事で使用するデータのスキーマを格納する役割を担っています。

class Device {

    @RowKey String id;
    Double lat;
    Double lon;

そして実際、GridDB Javaアプリケーションを書く際には、クラスを作成してスキーマをコンテナ内に配置することで、コンテナ用のスキーマを設定します。

GridDBクラス

実際のサーバエンドポイントの説明に入る前に、GridDBクラスのソースコードについて説明する必要があります。GridDB クラスは、基本的な機能をすべて担っており、エンドポイントが呼び出されると、単純に GridDB クラスのメソッドが実行されるように呼び出されます。

GridDBクラスは、GridDBのインスタンスに接続します。

public GridDB() {
        try {
        Properties props = new Properties();
        props.setProperty("notificationMember", "127.0.0.1:10001");
        props.setProperty("clusterName", "myCluster");
        props.setProperty("user", "admin"); 
        props.setProperty("password", "admin");
        store = GridStoreFactory.getInstance().getGridStore(props);
        devCol = store.putCollection("sbDEVICES", Device.class);
        } catch (Exception e) {
            System.out.println("Could not get Gridstore instance, exitting.");
            System.exit(-1);
        }
    }

先にも説明したように、このクラスは GridDB という名前であり、この場合、デフォルトのコンストラクタにはホストマシンの GridDB サーバが格納されています。つまり、GridDBクラスのインスタンスを生成するときには、すでにGridDBデータベースと通信できる状態になっている必要があります。

また、sbDEVICES という新しいコンテナも作成します(まだ存在しない場合)。このコンテナには、Device.class が提供するスキーマを使って、この記事のデータを格納します。

それはさておき、次は基本的なCRUDコマンドを担当する関数です。

デバイスコントローラ

前の2つのクラスに加えて、Spring Bootでは、プロジェクトで使用するエンドポイントを格納するDevice Controllerクラスも作成する必要があります。このファイルでは、特定のエンドポイント、メソッド、そして最後にコードが何をすべきかを設定します。

例えば、コントローラのデフォルトコンストラクタは、GridDBクラスのインスタンスを生成します。

@RestController
public class DeviceController {

    GridDB gridDb;

    public DeviceController() {
        super();
        gridDb = new GridDB();
    }

この時点から、関数を作成し、特定のエンドポイントやHTTPメソッドに関連付けるだけで、GridDBクラスのメソッドを呼び出して必要なことを実現できます。

APIエンドポイントの作成と呼び出し

これらのエンドポイントがどのように呼び出されるのかがもう少し分かったところで、実際にサーバーを動かして、いくつかの行を作成し、データベースから読み込み、最後にいくつかの行を削除してみましょう。

このプロジェクトでは、動作するフロントエンドを持っていないので、Postmanを使って、あるいは単にCLIとcurlコマンドを使ってAPIを弄ることができます。

POSTメソッド

まず始めに、データベースにいくつかのデータ行を作成する必要があります。これはもちろん、POSTリクエストを使ってオブジェクトデータをサーバーに送信する必要があることを意味します。

まず、この putDevice java 関数を使用して、実際にデータベースにデータ行を追加してみましょう。

public void putDevice(Device dev) {

        try {   
            System.out.println("dev="+dev);
            devCol.setAutoCommit(false);
            devCol.put(dev);
            devCol.commit();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

ここでは、単に Java API を使用して、HTTP リクエストのボディにあるユーザー生成データを直接 GridDB サーバーに .put しています。つまり、POSTリクエストのボディにはデバイスオブジェクト(id, lat, lon)が含まれていなければなりません。

POSTリクエスト

Device Controllerの実際のAPI Endpointのコードを見てみましょう。

@PostMapping("/device")
    @ResponseStatus(HttpStatus.CREATED)
    public ResponseEntity<?> postDevice(@RequestBody Device dev) {
        if (gridDb.getDevice(dev.getId()) == null) {
            gridDb.putDevice(dev);
            return new ResponseEntity<Device>(dev, HttpStatus.CREATED);
        } else {
            return new ResponseEntity<string>("Device already existsn", HttpStatus.BAD_REQUEST);
        }
    }</string>

このコードスニペットの一番上の行は、この関数に関連付けるHTTPメソッドをコントローラに伝えます。つまり、ユーザが http://localhost:8081/device を POST でコールすると、この行のコードがコールされることになります。

この場合、リクエストのボディを取得し、その情報を使ってgriddbクラスのメソッド .putDevice を実行し、コレクションコンテナ sbDEVICES に新しい行を作ります。以下はcurlコマンドを使ったコードです。

curl --location 'localhost:8081/device' 
--header 'Content-Type: application/json' 
--data '{
    "id": "1",
    "lat": 0,
    "lon": 0
}'

このコマンドを実行すると、サーバーから201レスポンスが返ってきて、データ行が利用可能になるはずです。

GETメソッド

次に、HTTPメソッドのGETで呼び出される関数を見てみましょう。GETは最もよく使われるシンプルなメソッドです(ウェブページを開くリクエストをして、ウェブページが返ってくると考えてください)。

GETを使えば、行が実際にデータベースに保存されたことを確認できます。

まず最初に、GridDBクラスのメソッド getDevicesgetDevice を紹介します。

public List<Device> getDevices() {
        List<Device> retval= new ArrayList<Device>();
        try {
            Query<Device> query = devCol.query("select *");
            RowSet<Device> rs = query.fetch(false);
            while(rs.hasNext()) {
                Device dev = rs.next();
                retval.add(dev);
            }
        } catch(Exception e) {
            e.printStackTrace();
        }
        return retval;

    }
    public Device getDevice(String id) {
        try {
            Query<Device> query = devCol.query("select * where id='"+id+"'");
            RowSet<Device> rs = query.fetch(false);
            if (rs.hasNext()) {
                Device dev = rs.next();
                return dev;
            }
        } catch(Exception e) {
            e.printStackTrace();
        }
        return null;

    }

上の関数は、選択したコンテナ内のすべての行を取得し、2番目の関数は、ユーザーが生成した行キーで特定の行を検索します。

リクエストの取得

データ行が利用可能であることを確認するために、GETメソッドを使ってデータベースから読み込むことができます。

まず、すべての行のデータを取得するエンドポイントです。

@GetMapping("/devices")
    public List<Device> getDevice() {
        return gridDb.getDevices();
    }

そして、特定の行を取得するものです。

@GetMapping("/device/{id}")
    public ResponseEntity<?> getDevice(@PathVariable String id) {
        Device dev = gridDb.getDevice(id);
        if (dev == null)
            return new ResponseEntity<string>("Device not foundn", HttpStatus.NOT_FOUND);
        else
            return new ResponseEntity<Device>(dev, HttpStatus.OK);
    }</string>
$ curl --location 'localhost:8081/devices'

この結果、リクエストしたデータとともに200 httpレスポンスが返ってくるはずです。

[{"id":"1","lat":0,"lon":0}]

データ行は1行だけですが、2つ目のエンドポイントを試してみましょう。

$ curl --location 'localhost:8081/device/1'
{"id":"1","lat":0,"lon":0}

また、2つ目のクエリは横の角括弧”[]”で囲まれていないことにお気づきでしょうか。これは、上記の関数が利用可能なすべての行を返すのに対し、2番目のクエリは単一の行を返すからです。

PUTメソッド

さて、ここで行の緯度と経度を更新したいとしましょう。POSTリクエストはエラー400 -- Device already existsを吐き出してしまうので使えません。行のデータを更新したい場合は、PUTリクエストを使用する必要があります。

ここで便利なのは、POSTメソッドとまったく同じ関数をPUTにも使えることです。主な注意点は、使用するHTTPメソッドを変更するようにデバイスコントローラーに指示する必要があることです。

PUTリクエスト

これが、PUT リクエストを処理する実際の Device Controller API のエンドポイントです。

@PutMapping("/device")
    public ResponseEntity<?> putDevice(@RequestBody Device dev) {
        Device dbdev = gridDb.getDevice(dev.getId());

        if (dbdev == null)
            return new ResponseEntity<string>("Device not foundn", HttpStatus.NOT_FOUND);

        if(dev.getLat() != null)
            dbdev.setLat(dev.getLat());
        if(dev.getLon() != null)
            dbdev.setLon(dev.getLon());
        
        gridDb.putDevice(dev);
        return new ResponseEntity<Device>(dbdev, HttpStatus.OK);
    }</string>

そして、それを呼び出し、いくつかの値を設定してみよう。

curl --location --request PUT 'localhost:8081/device' 
--header 'Content-Type: application/json' 
--data '{
    "id": "1",
    "lat": 33.8121,
    "lon": 117.9190
}'

サーバーが200コードで応答した場合、行は新しい値で更新されたと見なすことができます。GET`を実行して確認することもできます。

$ curl --location 'localhost:8081/device/1'
{"id":"1","lat":33.8121,"lon":117.919}

列が更新されました!

DELETEメソッド

前の関数と同様に、DELETEメソッドがあり、これはユーザーが作成した行キーに基づいて行を削除します。

    public void deleteDevice(String id) {

        try {   
            System.out.println("deleting dev="+id);
            devCol.setAutoCommit(false);
            devCol.remove(id);
            devCol.commit();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

この関数に適切な ID を与えることで、GridDB は sbDEVICES コンテナ内のデータ行を直接削除します。

DELETEリクエスト

最後に、行を削除して終わりにしましょう。

必要な情報は同じで、行のキー(ID)が必要なだけだからです。

@DeleteMapping("/device/{id}")
    public ResponseEntity<?> deleteDevice(@PathVariable String id) {
        if (gridDb.getDevice(id) != null) {
            gridDb.deleteDevice(id);
            return new ResponseEntity<string>("device deletedn", HttpStatus.ACCEPTED);
        } else {
            return new ResponseEntity</string><string>("Device does not yet exist. Nothing deleted.n", HttpStatus.BAD_REQUEST);
        }
    }</string>

適切なIDでこれを実行すると、コンテナから行が削除され、空のコンテナが残ります。

curl --location --request DELETE 'localhost:8081/device/1' 
--data ''

そしてもう1つ、GETを確認します。

$ curl --location 'localhost:8081/devices'
[]

結論

以上、GridDBサーバと安全にやりとりするためのSpring Boot CRUD APIエンドポイントを作成するメリットを紹介しました

If you have any questions about the blog, please create a Stack Overflow post here https://stackoverflow.com/questions/ask?tags=griddb .
Make sure that you use the “griddb” tag so our engineers can quickly reply to your questions.

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください