SpringBootでAPIサーバ構築
環境構築
- 以下の記事を参考にVScodeで環境構築
- 【簡単】VSCode+Spring Bootアプリケーション開発手順 | こへいブログ
参考動画
- この動画をベースに実装
- APIサーバを実装している(テンプレートの実装なし)
- Spring Boot Tutorial for Beginners (Java Framework) - YouTube
参考書籍
- 不明点の参照用として使用。とても噛み砕いて書かれていて分かりやすかった。 【後悔しないための入門書】Spring解体新書 Java入門のあとはこれを学ぶべき: Spring Boot2で実際に作って学べる!Spring Security、Spring JDBC、Spring MVC、Spring Test、Spring MyBatisなど多数解説! | 田村達也 | 工学 | Kindleストア | Amazon
ソースコード
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について
- 特に無くても成功したので現時点でなぜ必要なのか不明
- (特に参考にした記事)GitHub - FasterXML/jackson-docs: Documentation for the Jackson JSON processor.
- (公式)GitHub - FasterXML/jackson-databind: General data-binding package for Jackson (2.x): works on streaming API (core) implementation(s)
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 メソッド
- Listを取得するメソッド
- JdbcTemplate (Spring Framework 5.3.2 API) - Javadoc
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を定義
@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); } }
- pom.xml
<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
- 通常はクラス内に変数を作って、ymlファイルのプロパティを取り出しているっぽい
- Spring Bootで定義ファイル(yaml)を参照する | Developers.IO
HikariDataSource
- コネクションプールライブラリ。
コネクションプールとは
- 外部から DBにアクセスする際、DBMSとコネクションを確立する必要があるが、毎回それをやってたら負荷が掛かりすぎる。そのため一度確立されてたコネクションを保持して、システムの負荷を減らす仕組み。
- コネクションプーリング(コネクションプール)とは - IT用語辞典 e-Words
- クライアント型とサーバ型があり、よく使われてるのはアプリケーションでコネクションを管理するクライアント型(HikariCP)
- コネクションプーリングは実際必要なのか(PostgreSQL) | TECHSCORE BLOG
DataSourceBuilder
共通の実装とプロパティを持つ DataSourceSE を構築するための便利なクラス
- DataSourceBuilder (Spring Boot Docs 2.0.0.M1 API) - Javadoc
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とは
- ライブラリ等の依存関係を記載したファイル
- pom.xmlファイルの基本を理解しよう(1/4):初心者のためのApache Maven入門 - libro
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
- リクエストボディと引数をマッピングする
- spring — @RequestBodyと@RequestParamの違いは何ですか?
@PathVariable
- pathに渡されたパラメータと引数をマッピングする
@NotBlank
- バリデーションを実装可能
- 他にもメール形式チェックやURL形式チェック等のアノテーションが存在する
@Valid × @NotNull
@PutMapping(path = "{id}") public void updatePerson(@PathVariable("id") UUID id, @Valid @NotNull @RequestBody Person personToUpdate) { personService.updatePerson(id, personToUpdate); }
@Valid
でクラスに指定したバリデーションを有効にする- リクエストの中身がnullの場合バリデーションが実行されないため、
@NotNull
も併せて使用 - 参考記事
- ちなみにimportは以下参照
@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を付けない宣言
- クラスと同じアクセス範囲になる
- アクセス修飾子 | Javaコード入門
thisを付けずに自身の変数にアクセスする
- 基本付けなくて明示的に付ける必要がある時だけ付けるらしい
- Java - thisを必要な時と必要ではない時の違い|teratail
private public
- 以下記事に詳しくまとまっていた
- とほほのJava入門 - とほほのWWW入門
implements
- クラスにインターフェイスを実装する時に使用する
Map
- キーとバリューをペアで格納する
POJO
- ごく普通のjavaオブジェクトを強調した名前
Optional
- その値がnullかもしれないクラス
- Java 8 "Optional" ~ これからのnullとの付き合い方 ~ - Qiita
map
- Optional Objectの持つ値が非nullの場合、ブロック内のメソッドを実行する
- クラス java.util.Optionalのおさらいメモ - Qiita