gotoshin

主に学んだ事の自分メモ用です。記事に書くまでも無いような事はhttps://scrapbox.io/study-diary/に書いてます。

SpringBootでAPIサーバ構築

環境構築

参考動画

参考書籍

ソースコード

github.com

model

public class Person {
  private final UUID id;

  @NotBlank
  private final String name;

  public Person (@JsonProperty("id") UUID id,
                @JsonProperty("name") String name) {
    this.id = id;
    this.name = name;
  }

  public UUID getId() {
    return id;
  }

  public String getName() {
    return name;
  }

}

modelの役割

  • オブジェクトのプロパティやゲッター、セッターメソッドを定義する
  • オブジェクト=テーブルと理解している
@JsonPropertyについて

interfaceとしてのDao

public interface PersonDao {
  int insertPerson(UUID id, Person person);

  Optional<Person> selectPersonById(UUID id);

  List<Person> selectAllPeople();

}

interfaceとしてのDaoの役割

  • DB操作型メソッドのinterfaceを定義する
  • interfaceを用いる事で依存性を注入する

Dao

@Repository("postgress")
public class PersonDataAccessService implements PersonDao {

  private final JdbcTemplate jdbcTemplate;

  @Autowired
  public PersonDataAccessService(JdbcTemplate jdbcTemplate) {
    this.jdbcTemplate = jdbcTemplate;
  }

  @Override
  public List<Person> selectAllPeople() {
    final String sql = "SELECT id,name FROM person";
    return jdbcTemplate.query(sql, (resultSet, i) -> {
      UUID id = UUID.fromString(resultSet.getString("id"));
      String name = resultSet.getString("name");
      return new Person(id, name);
    });
  }

  @Override
  public Optional<Person> selectPersonById(UUID id) {
    final String sql = "SELECT id,name FROM person WHERE id = ?";
    Person person = jdbcTemplate.queryForObject(
      sql,
      new Object[]{id},
      (resultSet, i) -> {
        UUID personId = UUID.fromString(resultSet.getString("id"));
        String name = resultSet.getString("name");
        return new Person(personId, name);
    });
    return Optional.ofNullable(person);
  }
}

Daoの役割

  • Data Access Objectの略で実際のDBを操作する処理を定義する

DIコンテナ登録

@Repository("postgress")
  • postgressという名前でコンテナに登録する

コンストラク

  @Autowired
  public PersonDataAccessService(JdbcTemplate jdbcTemplate) {
    this.jdbcTemplate = jdbcTemplate;
  }
  • jdbcTemplateはデフォルトでインスタンスがDIコンテナに登録されており、それと紐付け(と理解した)

JdbcTemplateとは

  • spring jdbcが提供するクラス
  • このクラスを介してDBを操作することで、接続やDB毎に違うエラー処理等を良い具合にしてくれる

JdbcTemplate query メソッド

JdbcTemplate queryForObject メソッド

- 一意のデータを取得するメソッド

new Object[]

sevice

@Service
public class PersonService {

  private final PersonDao personDao;

  @Autowired
  public PersonService(@Qualifier("postgress") PersonDao personDao) {
    this.personDao = personDao;
  }

  public List<Person> getAllPeople() {
    return personDao.selectAllPeople();
  }

  public Optional<Person> getPersonById(UUID id) {
    return personDao.selectPersonById(id);
  }

}

Serviceの役割

  • daoとcontrollerを結びつける役割
  • controllerとdaoを疎結合にすることが出来る

DIコンテナ登録

@Service
public class PersonService {

コンストラク

@Qualifier("postgress") 
  • 先程定義したDAOを取得

Controller

@RequestMapping("/api/v1/person")
@RestController
public class PersonController {

  private final PersonService personService;

  @Autowired
  public PersonController(PersonService personService) {
    this.personService = personService;
  }

  @GetMapping
  public List<Person> getAllPeople() {
    return personService.getAllPeople();
  }

  @GetMapping(path = "{id}")
  public Person getPersonById(@PathVariable("id") UUID id) {
    return personService.getPersonById(id)
                        .orElse(null);
  }

コンストラク

  @Autowired
  public PersonController(PersonService personService) {
    this.personService = personService;
  }
  • 先程定義したseerviceを取得

@RestController

  • 戻り値のメソッドにhtmlファイル以外を指定可能

戻り値のJSON自動変換

  • オブジェクトをメソッドの戻り値として指定すると、自動でJSON形式に変換される

バリデーション

  • 実装したが動画の通りうまくいかず。
  • Valid→Validatedへ変更してみるもNG
  • こういうものがあるんだ!という事が分かったので今回はよしとする

DB接続 環境構築

パッケージを追加

必要なパッケージを追加

pom.xmlに追加
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>

    <dependency>
      <groupId>org.postgresql</groupId>
      <artifactId>postgresql</artifactId>
      <scope>runtime</scope>
    </dependency>

    <dependency>
      <groupId>org.flywaydb</groupId>
      <artifactId>flyway-core  </artifactId>
    </dependency>

DBのコンテナ環境作成

  • DBの環境をdockerで作成
  • postgres-sqlの環境を構築
コマンドで作成
docker run --name postgres-spring -e POSTGRES_PASSWORD=password -d -p 5432:5432 postgres:alpine

DB接続設定を定義

  • src/main/resources/aplication.yml
app:
  datasorce:
  jdbc-url: jdbc:postgressql://localhost:5432/demodb
  username: postgres
  password: pwssword
  pool-size: 30
  • 上記で作成したコンテナが5432portを使用しているため、localhost5432を読込

Datasourceを定義

  • src/main/java/com/example/demo2/datasorce/PostgressDatasorce.java
@Configuration
public class PostgressDatasorce {

  @Bean
  @ConfigurationProperties("app.datasource")
  public HikariDataSource hikariSource() {
    return DataSourceBuilder
              .create()
              .type(HikariDataSource.class)
              .build();
  }
}
  • 動画のコードはこの通りだが、なぜか自分の環境では上手く動かず・・・
  • 以下公式に書いてあるymlを読み込まない方式で実施(結構ハマってしまった)
@Configuration
public class PostgressDatasource {

  @Bean
  public HikariDataSource hikariDataSource() {
    // HikariCPの初期化
    HikariConfig config = new HikariConfig();
    config.setJdbcUrl("jdbc:postgresql://localhost:5432/demodb");
    config.setUsername("postgres");
    config.setPassword("password");
    return new HikariDataSource(config);
  }
}
    <dependency>
      <groupId>com.zaxxer</groupId>
      <artifactId>HikariCP</artifactId>
      <version>3.4.5</version>
    </dependency>
要するに何をやってるの?
  • DataSourceオブジェクトを返す、PostgressDatasorceをDIコンテナに登録
  • @ConfigurationProperties("app.datasource")aplication.ymlの中身を読込
  • HikariDataSourceをコネクションプールライブラリに指定して、DBのインスタンスをアプリケーション内に作成する
@ConfigurationProperties
HikariDataSource
コネクションプールとは
DataSourceBuilder
DataSource
  • このDataSourceオブジェクトが表す物理データ・ソースへの接続に対するファクトリです

  • つまりDBをオブジェクト化したもの
  • DataSource (Java Platform SE 8)

sqlを作成

以下にファイルを作成
  • src/main/resources/db/migration/V1__PersonTable.sql
  • 中身
CREATE TABLE person (
  id UUID NOT NULL PRIMARY KEY,
  name VARCHAR(100) NOT NULL
);

DBを作成

コンテナ内へ接続
docker exec -it 83cf4515a6a6 /bin/bash
DB内へ接続
psql -U postgres
  • ymlファイル内のusername: postgresに合わせる
  • postgresの中に入る
databaseを作成
 CREATE DATABASE demodb;

-ymlファイル内の jdbc-url: jdbc:postgressql://localhost:5432/demodbと合わせる

結果を確認
\l
  • demodbが作成されていることを確認
demodbへ接続
\c demodb
DBの中身を確認
\d
  • アプリケーション起動前は空
  • アプリケーション起動後はmigrationが実行され、personテーブルが作成されている
データを手動で作成
CREATE EXTENSION uuid-ossp
  • CREATE EXTENSIONで拡張をインストールする
  • uuid-osspはユニークなIDを生成する関数
  • 後はデータを手動で登録してgetすればデータが取得出来る

Springの書き方について

依存性と注入

依存性とは
  • あるクラスA内でクラスBを使用している場合、クラスAはクラスBに依存している
  • クラスAでクラスCを使用するように変更となった場合、クラスA内でクラスB独自のメソッド等を使用していた場合、クラスA内の変更範囲が大きくなる
  • クラスA内の影響範囲を最小限に留めるために、クラスB・Cをインターフェイスを用いて、統一された枠組みの中で生成するとクラスA内の変更範囲を少なくする事が出来る(疎結合にすることが出来る)
注入とは

DIコンテナとは

1.管理対象のクラスを探す(コンポーネントスキャン)
  • Springを起動すると、コンポーネントスキャンという処理が走って、DI管理対象のクラス(アノテーションが付与されているクラス)を探す
  • 上記の対象アノテーション@Component @Contriller @Service @Repository @Configuration @RestController @ControllAdvice @ManagedBean @Named
  • アノテーションが付与されたクラスの事をBeanと呼ぶ(DIコンテナ上で管理する対象のクラスをBean と呼ぶため)
2.インスタンスの生成と注入
  • DI管理対象のクラスを認識した後、それらのインスタンスを生成する
  • そして@Autowiredアノテーションが付与されたフィールドなどに注入(代入)する
  • イメージとしてはDIコンテナ内でインスタンスを管理し、@AutowiredはDIコンテナ内の対象インスタンスのgetterを呼び出しているイメージ
要するに何がうれしいの?
JavaConfigベースでの登録方法
  • @Configurationアノテーションを付与したクラスを生成する
  • そのクラスの中に@Beanアノテーションを付与したgetterクラスを用意する
  • これでgetterの戻値がDIコンテナに登録される(Autowired等で使用出来る)

pom.xmlとは

Apache Mavenとは

ログの出し方

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class FakePersonDataAccessService implements PersonDao {
  private static final Logger logger = LoggerFactory.getLogger(FakePersonDataAccessService.class);
  public int insertPerson(UUID id, Person person) {
    logger.info("post person");

2021/01/11 追記

System.out.print("文字列");
  • これでいけた・・・

jar

  • javaの圧縮ファイル

アノテーション

@RequestBody
@PathVariable
@NotBlank
  • バリデーションを実装可能
  • 他にもメール形式チェックやURL形式チェック等のアノテーションが存在する
@Valid × @NotNull
  @PutMapping(path = "{id}")
  public void updatePerson(@PathVariable("id") UUID id, @Valid @NotNull @RequestBody Person personToUpdate) {
    personService.updatePerson(id, personToUpdate);
  }
@Repository("postgress")
  • DIに登録される
  • @Controllerとの機能的な違いは無く、分かりやすく分けているだけ
@Qualifier
  • どのBeanをAutowiredするか指定出来る
@Autowired
  • フィールドに付ける事でDIコンテナからインスタンスを取得してくる

Javaの書き方について

コンストラクタ内でのthisの役割

  private final PersonDao personDao;

  public PersonService(PersonDao personDao) {
    this.personDao = personDao;
  }
  • this.personDao = personDao;において、左辺はprivate final PersonDao personDao;、右辺は引数で受け取ったPersonDao personDaoを示す

default

  • interface内で処理の中身を実装可能
  • 継承先で中身を実装する必要がなくなる

public privateを付けない宣言

thisを付けずに自身の変数にアクセスする

private public

implements

Map

  • キーとバリューをペアで格納する

POJO

  • ごく普通のjavaオブジェクトを強調した名前

Optional

map

List.of