JavaRush /Java Blog /Random-JA /PostgreSQL データベースを Spring Boot の RESTful サービスに追加します。パート2
Artur
レベル 40
Tallinn

PostgreSQL データベースを Spring Boot の RESTful サービスに追加します。パート2

Random-JA グループに公開済み
PostgreSQL データベースを Spring Boot の RESTful サービスに追加します。パート 1 PostgreSQL データベースを Spring Boot の RESTful サービスに追加します。 パート 2 - 1最後のパートでは、コンピューターに PostgresSQL データベースをインストールし、pgAdmin でデータベースを作成し、手動およびプログラムでデータベース内のテーブルを作成および削除する方法を学びました。このパートでは、このデータベースとテーブルの操作を学習できるようにプログラムを書き直します。なんで私達なの?私自身もこの教材で皆さんと一緒に学んでいるからです。そして、目の前のタスクを解決するだけでなく、経験豊富なプログラマーからのアドバイスを活用して、外出中に発生したエラーを修正します。いわば、チームで作業することを学びます ;) まず、com.javarush.lectures.rest_exampleフォルダー内に新しいパッケージを作成し、それを と呼びましょうrepository。このパッケージでは、新しいインターフェースを作成しますClientRepository
package com.javarush.lectures.rest_example.repository;

import com.javarush.lectures.rest_example.model.Client;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ClientRepository extends JpaRepository<Client, Integer>  {
}
このインターフェイスは、データベースやテーブルと「魔法のように」対話します。なぜ魔法のように?なぜなら、その実装を記述する必要がなく、Spring フレームワークがそれを提供してくれるからです。このようなインターフェイスを作成するだけで、すでにこの「魔法」を使用できるようになります。Client次のステップでは、次のように クラスを編集します。
package com.javarush.lectures.rest_example.model;

import javax.persistence.*;

@Entity
@Table(name = "clients")
public class Client {
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column(name = "name")
    private String name;

    @Column(name = "email")
    private String email;

    @Column(name = "phone")
    private String phone;

    //… getters and setters
}
このクラスで行ったのは、注釈を追加しただけです。それらを見てみましょう:
  • @Entity - この Bean (クラス) がエンティティであることを示します。
  • @Table - このエンティティに表示されるテーブルの名前を示します。
  • @Id - 列 ID (主キー - 現在のテーブル内のデータの一意性を保証するために使用される値。注: Andrei )
  • @Column - エンティティ プロパティにマップされる列の名前を示します。
  • @GeneratedValue - このプロパティが指定された戦略に従って生成されることを示します。
テーブルのフィールドの名前は、クラス内の変数の名前と一致する必要はありません。たとえば、変数がある場合はfirstName、テーブル内のフィールドに名前を付けますfirst_name。これらのアノテーションは、フィールドとそのゲッターの両方に直接設定できます。ただし、これらの方法のいずれかを選択した場合は、プログラム全体を通じてこのスタイルを維持するようにしてください。最初の方法はリストを短くするためだけに使用しました。データベースを操作するためのアノテーションのより完全なリストは、ここにあります。次に、クラスに移動してClientServiceImpl、次のように書き直してみましょう。
package com.javarush.lectures.rest_example.service;

import com.javarush.lectures.rest_example.model.Client;
import com.javarush.lectures.rest_example.repository.ClientRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class ClientServiceImpl implements ClientService {

    @Autowired
    private ClientRepository clientRepository;

    @Override
    public void create(Client client) {
        clientRepository.save(client);
    }

    @Override
    public List<Client>  readAll() {
        return clientRepository.findAll();
    }

    @Override
    public Client read(int id) {
        return clientRepository.getOne(id);
    }

    @Override
    public boolean update(Client client, int id) {
        if (clientRepository.existsById(id)) {
            client.setId(id);
            clientRepository.save(client);
            return true;
        }

        return false;
    }

    @Override
    public boolean delete(int id) {
        if (clientRepository.existsById(id)) {
            clientRepository.deleteById(id);
            return true;
        }
        return false;
    }
}
リストからわかるように、必要なくなった行を削除しただけです。
// Хранorще клиентов
private static final Map<Integer, Client>  CLIENT_REPOSITORY_MAP = new HashMap<>();

// Переменная для генерации ID клиента
private static final AtomicInteger CLIENT_ID_HOLDER = new AtomicInteger();
代わりにインターフェイスを宣言し、その上に@AutowiredClientRepositoryアノテーションを配置して、Spring がこの依存関係をクラスに自動的に追加できるようにしました。また、すべての作業をこのインターフェイス、つまり Spring が追加するその実装に委任しました。最後の最も興味深い段階、つまりアプリケーションのテストに進みましょう。Postman プログラムを開き (使用方法はこちらを参照)、GET リクエストを次のアドレスに送信しましょう: http://localhost:8080/clients。次のような答えが得られます。
[
    {
        "id": 1,
        "name": "Vassily Petrov",
        "email": "vpetrov@jr.com",
        "phone": "+7 (191) 322-22-33)"
    },
    {
        "id": 2,
        "name": "Pjotr Vasechkin",
        "email": "pvasechkin@jr.com",
        "phone": "+7 (191) 223-33-22)"
    }
]
POST リクエストを送信します。
{
  "name" : "Amigo",
  "email" : "amigo@jr.com",
  "phone" : "+7 (191) 746-43-23"
}
そして...プログラムで最初のバグを発見しました。
{
    "timestamp": "2020-03-06T13:21:12.180+0000",
    "status": 500,
    "error": "Internal Server Error",
    "message": "could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement",
    "path": "/clients"
}
PostgreSQL データベースを Spring Boot の RESTful サービスに追加します。 パート 2 - 2 ログを確認すると、次のエラーが見つかります。

org.postgresql.util.PSQLException: ОШИБКА: повторяющееся meaning ключа нарушает ограничение уникальности "clients_pkey"
  Detail: Ключ "(id)=(1)" уже существует.
同じ POST リクエストを再度送信すると、結果は同じですが、次のような違いがあります。Ключ "(id)=(2)" уже существует. 同じリクエストを 3 回目に送信すると、Status: 201 Created が返されます。再度 GET リクエストを送信し、応答を受け取ります。
[
    {
        "id": 1,
        "name": "Vassily Petrov",
        "email": "vpetrov@jr.com",
        "phone": "+7 (191) 322-22-33)"
    },
    {
        "id": 2,
        "name": "Pjotr Vasechkin",
        "email": "pvasechkin@jr.com",
        "phone": "+7 (191) 223-33-22)"
    },
    {
        "id": 3,
        "name": "Amigo",
        "email": "amigo@jr.com",
        "phone": "+7 (191) 746-43-23"
    }
]
これは、プログラムがこのテーブルがすでに事前に設定されているという事実を無視し、1 から始まる ID を再度割り当てていることを示唆しています。まあ、バグは問題が発生する瞬間です。絶望しないでください。これはよく起こります。したがって、私はより経験豊富な同僚に助けを求めます。「同僚の皆様、プログラムが正常に動作するようにこの問題を修正する方法をコメントでアドバイスしてください。」助けが到着するまでにそれほど時間はかかりませんでした。そして、スタス・パシンコフがコメントで私がどの方向に目を向けるべきかを教えてくれました。これについては彼に特に感謝します! しかし、問題は、クラスでフィールドのClientアノテーションの戦略を誤って指定したことです。 この戦略は MySQL に適しています。Oracle または PostrgeSQL を使用する場合は、別の戦略を設定する必要があります。主キーの戦略について詳しくは、こちらをご覧ください。私は GenerationType.SEQUENCE 戦略を選択しました。これを実装するには、initDB.sql ファイルと、もちろん Client クラスの id フィールドの注釈を少し書き直す必要があります。initDB.sql を書き換えます。 @GeneratedValue(strategy = GenerationType.IDENTITY)idPostgreSQL データベースを Spring Boot の RESTful サービスに追加します。 パート 2 ~ 3
CREATE TABLE IF NOT EXISTS clients
(
    id    INTEGER PRIMARY KEY ,
    name  VARCHAR(200) NOT NULL ,
    email VARCHAR(254) NOT NULL ,
    phone VARCHAR(50)  NOT NULL
);
CREATE SEQUENCE clients_id_seq START WITH 3 INCREMENT BY 1;
変更点: テーブルの id 列のタイプが変更されましたが、それについては後ほど説明します。以下に新しいシーケンス client_id_seq を作成する行を追加し、それが 3 で始まることを示し (populateDB.sql ファイルの最後の ID が 2 であるため)、増分が 1 ずつ発生することを示します。id 列の型に戻りましょう。ここで INTEGER を指定したのは、SERIAL のままにすると、シーケンスが同じ名前 client_id_seq で自動的に作成されますが、1 から開始されるためです (これがプログラムのバグにつながりました)。ただし、テーブルを削除する場合は、次のコマンドを使用して、pgAdmin インターフェイスを介して手動で、または .sql ファイルを介してこのシーケンスをさらに削除する必要があります。
DROP TABLE IF EXISTS clients;
DROP SEQUENCE IF EXISTS clients_id_seq
ただし、最初にテーブルにデータを設定するために PopulateDB.sql などのファイルを使用しない場合は、主キーに SERIAL または BIGSERIAL タイプを使用でき、シーケンスを手動で作成する必要がないため、削除する必要もありません。それは別にして。シーケンスの詳細については、の Web サイトで読むことができます。PostgreSQL ドキュメントidクラスフィールドの注釈に進みClient、次のようにフォーマットしてみましょう。
@Id
@Column(name = "id")
@SequenceGenerator(name = "clientsIdSeq", sequenceName = "clients_id_seq", allocationSize = 1)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "clientsIdSeq")
private Integer id;
@SequenceGenerator私たちがやったこと:シーケンス ジェネレーターを作成するための 新しいアノテーションをインストールし、それに名前を割り当てclientsIdSeq、これがシーケンスのジェネレーターであることを示しclients_id_seq、属性を追加しました。allocationSize = 1 これはオプションの属性ですが、これを行わないと、プログラムを実行すると、次のエラーが表示されます。

org.hibernate.MappingException: The increment size of the [clients_id_seq] sequence is set to [50] in the entity mapping while the associated database sequence increment size is [1]
これについてユーザーAndrei がコメントで 書いているの は次のとおりです。allocationSizeは主に、「新しい ID」を求めて Hibernate がデータベースにアクセスする回数を減らすことを目的としています。値 == 1 の場合、新しいエンティティごとに休止状態になり、データベースに保存する前に、その ID のデータベースに対して「実行」されます。値が > 1 (たとえば、5) の場合、Hibernate はデータベースに「新しい」ID を問い合わせる頻度を減らし (たとえば、5 回)、問い合わせるときに、Hibernate はデータベースにこの番号を予約するように要求します (例:ケース、5) 値。あなたが説明したエラーは、hibernate が 50 個のデフォルト ID を受け取りたいことを示唆していますが、データベースでは、最初のものに従ってのみこのエンティティの ID を発行する準備ができていることを示しました別のバグがユーザーNikolya Kudryashovによって捕捉されました。元の記事 http://localhost:8080/clients/1 からリクエストを実行すると、エラーが返されます。
{
    "timestamp": "2020-04-02T19:20:16.073+0000",
    "status": 500,
    "error": "Internal Server Error",
    "message": "Type definition error: [simple type, class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: com.javarush.lectures.rest_example.model.Client$HibernateProxy$ZA2m7agZ[\"hibernateLazyInitializer\"])",
    "path": "/clients/1"
}
このエラーは Hibernate の遅延初期化に関連しており、これを取り除くには、Client クラスに追加のアノテーションを追加する必要があります。
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
このようにして:
@Entity
@Table(name = "clients")
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Client {.....}
次に、プログラムを実行して (前回からデータベースにクライアント テーブルが残っている場合はデータベースから削除した後)、application.properties ファイルから 3 行をコメント アウトします。
#spring.datasource.initialization-mode=ALWAYS
#spring.datasource.schema=classpath*:database/initDB.sql
#spring.datasource.data=classpath*:database/populateDB.sql
前回は最後の行だけコメントしましたが…。すでにテーブルを作成して記入しているので、現時点ではこれがより論理的であるように思えました。テストに進み、Postman 経由で GET、POST、PUT、DELETE リクエストを実行すると、バグが消え、すべてが正常に動作していることがわかります。以上です、仕事は終わりました。ここで、学んだことを簡単にまとめて検討してみましょう。
  • コンピュータに PostgreSQL をインストールする
  • pgAdminでデータベースを作成する
  • テーブルを手動およびプログラムで作成および削除する
  • .sql ファイルを介してテーブルにデータを取り込む
  • Spring フレームワークの「魔法の」 JpaRepository インターフェースについて少し学びました
  • このようなプログラムを作成するときに発生する可能性のあるいくつかのバグについて学びました
  • 同僚にアドバイスを求めるのは恥ずかしいことではないことに気づきました
  • 私たちは、JavaRush コミュニティが常に助けに来てくれる力であることを確認しました ;)
とりあえずここで終了です。PostgreSQL データベースを Spring Boot の RESTful サービスに追加します。 パート 2 ~ 4この資料を読んでくださった皆様、ありがとうございました。皆様のコメント、ご意見、追加、建設的な批判をお待ちしております。おそらくあなたは、この問題に対してより洗練された解決策を提案してくれるでしょう。もちろん、あなたが著者であることを明記して、UPD の「キーワード」を使用してこの記事に追加することをお約束します。まあ、一般的に、この記事とこの資料の提示スタイルが気に入ったかどうか、そして一般的に、JR に関する記事を書き続ける必要があるかどうかを書いてください。追加点は次のとおりです。 UPD1: ユーザーJustinian は、Java の命名規則に違反しないように、パッケージの名前com.javarush.lectures.rest_exampleを に変更しcom.javarush.lectures.rest.example、プロジェクトの名前を変更することを強く推奨しました。UPD2 ユーザーのAlexander Pyanov はClientRepository、クラス内のフィールドを初期化するには、ClientServiceImplアノテーションよりもコンストラクターを使用する方が良いと提案しました@Autowired。これは、まれに が発生する可能性があるという事実によって説明されますがNullPointerException、一般的にはこれがベスト プラクティスであり、私もそれに同意します。論理的には、オブジェクトの初期機能にフィールドが必要な場合は、コンストラクター内でフィールドを初期化することをお勧めします。コンストラクターのないクラスはオブジェクトにアセンブルされないため、このフィールドはその段階で初期化されます。オブジェクト作成の様子。修正を加えたコード フラグメントを追加します (何を置き換える必要があるか)。
@Autowired
private ClientRepository clientRepository;

private final ClientRepository clientRepository;

public ClientServiceImpl(ClientRepository clientRepository) {
   this.clientRepository = clientRepository;
}
最初の部分へのリンク: Spring Boot の RESTful サービスへの PostgreSQL データベースの追加。パート 1 追記 この教育アプリケーションの開発を続けたいと考えている方がいらっしゃいましたら、この記事にガイドへのリンクを喜んで追加させていただきます。おそらくいつかこのプログラムは、ポートフォリオに作業を追加できる実際のビジネス アプリケーションに似たものに成長するでしょう。PPS この控えめな記事に関して、私はこのペンのテストを私たちの親愛なる女の子、女性、女性に捧げることにしました。この女性がいなかったら、おそらく今では Java も JavaRush もプログラミングも存在しなかったでしょう。賢い皆さん、休暇おめでとうございます! 3月8日おめでとうございます!幸せで美しくなってください!
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION