🐥

Java + Spring Boot + PostgreSQL + Spring Data REST でREST API

2020/12/20に公開3

ついにタイトルに docker-compose が入らなくなりました笑

TL;DR

Spring Data REST を使う準備

Spring Data REST というのは下記記事でも少し触れたのですが、
Entity と Repository を用意するだけで
RESTful な Endpoint が全部生える便利なパッケージです。

ので準備といっても前回の記事の状態から server/build.gradle に
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
を追記するだけです。

server/build.gradle
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-rest'
...

Spring Initializr だとこんな感じですね。

前回の記事の状態からコードの変更も不要です。すごい(小学生並みの感想)

Docker コンテナを起動してGradleビルド, アプリケーション起動, リクエストを投げてみる

docker-compose up -d
# DB が立ち上がって初期化されるまでちょっとかかるのでちょっと待つ
docker-compose exec app bash
bash-4.4# sh gradlew build
...
BUILD SUCCESSFUL in 8m 41s
5 actionable tasks: 5 executed
# できてるのを確認
bash-4.4# ls build/libs/
app-0.0.1-SNAPSHOT.jar
bash-4.4# java -jar build/libs/app-0.0.1-SNAPSHOT.jar

起動したらリクエストを投げてみましょう。

$ curl http://localhost:8080/users -X GET
{
  "_embedded" : {
    "users" : [ {
      "name" : "test",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/users/1"
        },
        "user" : {
          "href" : "http://localhost:8080/users/1"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/users"
    },
    "profile" : {
      "href" : "http://localhost:8080/profile/users"
    }
  }
}

取れてる! すごい!
POST リクエストでデータの登録もできます。

$ curl http://localhost:8080/users -X POST -H "Content-Type:application/json" -d '{ "name":"foo" }'
{
  "name" : "foo",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/users/2"
    },
    "user" : {
      "href" : "http://localhost:8080/users/2"
    }
  }
}
$ curl http://localhost:8080/users -X POST -H "Content-Type:application/json" -d '{ "name":"bar" }'
{
  "name" : "bar",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/users/3"
    },
    "user" : {
      "href" : "http://localhost:8080/users/3"
    }
  }

再度 GET リクエストしてみましょう。

curl http://localhost:8080/users -X GET
{
  "_embedded" : {
    "users" : [ {
      "name" : "test",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/users/1"
        },
        "user" : {
          "href" : "http://localhost:8080/users/1"
        }
      }
    }, {
      "name" : "foo",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/users/2"
        },
        "user" : {
          "href" : "http://localhost:8080/users/2"
        }
      }
    }, {
      "name" : "bar",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/users/3"
        },
        "user" : {
          "href" : "http://localhost:8080/users/3"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/users"
    },
    "profile" : {
      "href" : "http://localhost:8080/profile/users"
    }
  }
}

id をURIで指定することでその id のデータだけをGETすることももちろん可能です。

$ curl http://localhost:8080/users/3 -X GET
{
  "name" : "bar",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/users/3"
    },
    "user" : {
      "href" : "http://localhost:8080/users/3"
    }
  }
}

PUT リクエストで更新もできます。

$ curl http://localhost:8080/users/3 -X PUT -H "Content-Type:application/json" -d '{ "name":"piyo" }'
{
  "name" : "piyo",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/users/3"
    },
    "user" : {
      "href" : "http://localhost:8080/users/3"
    }
  }
}
$ curl http://localhost:8080/users/3 -X GET
{
  "name" : "piyo",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/users/3"
    },
    "user" : {
      "href" : "http://localhost:8080/users/3"
    }
  }
}

ワーオ。ワーオですねこれは。
http://localhost:9000 で Adminer にログインして
データベースにも登録されてるのか見てみましょう。

登録されてますね! OKです!
超簡単な RESTfull API を用意するのなら Rails より早いのではないでしょうか……?笑

トラブルシュート:POSTで登録できない時

POST リクエストを投げた時にコードの状態によっては 500 ERROR になることがあります。
どういうエラーなのかは java -jar build/libs/app-0.0.1-SNAPSHOT.jar を実行して
Spring Boot アプリケーションが立ち上がってる方にログが出てるはずです。

ERROR:relation "hibernate_sequence" does not exist

PostgreSQL の SERIAL 列に対してEntityクラスの @GeneratedValue の対応付けが間違っている可能性があります。
今回のコードであれば以下の部分を確認してみてください。

server/src/main/java/com/example/app/entity/User.java
package com.example.app.entity;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "users")
public class User {

  @Id
  // ここが @GeneratedValue(strategy = GenerationType.IDENTITY) かを確認
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  private String name;

  protected User() {}

  public User(String name) {
    this.name = name;
  }

  public Long getId() {
    return id;
  }

  public String getName() {
    return name;
  }

  @Override
  public String toString() {
    return String.format("{id:%d,name:%s}", id, name);
  }
}

ERROR:permission denied for sequence user_id_seq

今回は appuser というユーザを作成して appサーバーから PostgreSQL に接続していますが、
appuser に SEQUENCES に対して SELECT 権限がないと SERIAL 列に id のインクリメント発行時にエラーになります。

今回のコードであれば以下の部分を確認してみてください。

forDocker/db/initdb/3_create_role_appuser.sql
CREATE ROLE appuser WITH LOGIN PASSWORD 'apppass';
GRANT SELECT,UPDATE,INSERT,DELETE ON ALL TABLES IN SCHEMA public TO appuser;
-- この下の行がちゃんとあるか確認
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO appuser;

なお、forDocker/db/initdb 配下のクエリを編集した場合は
今回の docker-compose.yml であれば docker volume を割り当てているので
dbvol を docker volume rm ... で削除してやれば再度実行させることができます。


今回のリポジトリはこちらです。
https://github.com/JUNKI555/java_spring_boot_practice02

参考

Discussion

北山淳也北山淳也

「今回のコードであれば以下の部分を確認してみてください。」とか
偉そうに書いてるけど今回の記事を書き始めてから前回の記事を修正したのは内緒です(これが原因でハマッた人がいたらごめんなさい、のでトラブルシュートとして書きました🙇‍♂️)

catnosecatnose

タイトル制限とりあえず近いうちに+10字します…😹

北山淳也北山淳也

@catnose さん
ありがとうございます!私ももうちょっとシンプルにタイトルつけるようにします 🙇‍♂️