okinawa

IT勉強メモ

SQL勉強メモ

SQL関連で勉強したことのまとめ記事。
長くなってきたのでリンク集として使っていきたい。

SQL実行順序

FROM
JOIN
WHERE
GROUP BY
SUM, MAXなど
HAVING
SELET,DISTINCT
ORDER BY
LIMIT

ちなみにサブクエリは内側から最初に実行される。

小技集

select 1

select 1 from table~という謎のSQLに出会った。

exists文で使われるものらしい。

存在するかどうかだけがわかればよくて、データは要らないよって時に使う。

ただ、select 1 より select * のほうが早いらしい↓
COUNT(*)とCOUNT(1)の処理速度検証結果

もっと言うとexistsよりjoinのほうが早いらしい↓
EXISTSとSQLの高速化について - 猫好きモバイルアプリケーション開発者記録

-- A.name = B.nameである行だけ抽出
select * from Atable AS A
where
exists(
    select 1 from Btable AS B
    where
    A.name = B.name
    )
-- -- A.name = B.nameではない行だけ抽出
select * from Atable AS A
where
not exists(
    select 1 from Btable AS B
    where
    A.name = B.name
    )

where 1 = 1

where はtrueかfalseかで判定している。

1 = 1なら常にtrue。

使い所はwhere句かand句かの分岐の時。

よく複数単語検索なんかで使う。

検索条件が増える度にAND句で追加したいんだけど、最初はWHERE句じゃないといけないのでめんどくさい。

そこで WHERE句を1=1にして、AND句の追加だけで良いようにする。

select * from
where 1= 1
and~ // ここに追加していく

・参考サイト
「WHERE 1=1」は条件付きSQL文が書きやすくなる魔法の言葉 | キノコログ

OFFSET FETCH

MySQLのLMIT OFFSETと同じで取得する行数を指定できる。

OFFSETでスキップする行数を指定。
FETCH NEXT ○ ROWS ONLYで何行取得するかを指定。

select user_id, name from account_table
order by user_id
OFFSET 10 ROWS
FETCH NEXT 5 ROWS ONLY;

11行~15行を取得。

・参考

sql-oracle.com

count(*)over()

window関数。

ページングの時にとても便利!

selectで総レコード数を取得しつつ、1ページ分のselect結果も欲しいときに使える。

secect count(*)over() , account_name
from account
limit 0 rows
fetch 100 rows

・参考記事

resanaplaza.com

テーブルデータ増殖技(Insert Select)

増殖テーブル

SQL
id = A を id = B にしてそれ以外の列はそのままで増殖する。

insert into increse
select 
name,
'B'
from increse
where
id = 'A'

増殖後

イメージとしては下記SQLを実行している感じ。
INSERT INTO table (name, id) VALUES (SELECT文)

insert into increse
(name, id)
values
(select name, 'C' AS 'id' from increse
where 
id = 'B')

GROUP BY句にないカラムも表示させたい

Group By句にないカラムを表示させたい - okinawa

テーブル同士の比較

【SQL】テーブル同士の比較 - okinawa

ファイル取り込み(bulk insert)

テキストやCSVファイルをDBにインポートするときに使う。

SQL Server で BULK INSERT をつかってcsvなどのデータを取込む方法 #SQL - Qiita

テーブルコピー(select * into)

SELECT * INTO コピー先テーブル名 FROM コピー元テーブル名;

基礎知識

GROUP BYってなんなの?

部分集合に切り分ける。
GROUP BYってなんなの? - okinawa

NULLとの四則演算は必ずNULLになる

1 + NULL = NULL
5 * NULL = NULL

集合指向言語と手続き型言語の違い

集合指向言語:まとめて処理
手続き型言語:1件ずつ処理
集合指向言語と手続き型言語の違い - okinawa

HAVING句を理解すると集合志向が理解できるらしい

HAVING句をマスターすると集合志向が理解できるらしい - okinawa

3値論理

普通は TRUE/FALSEの2つ
SQLは TRUE / FALSE / UNKNOWN の3つ
真理値の優先順位は下記↓

  • AND:FALSE > UNKNOWN > TRUE
  • OR: TRUE > UNKNOWN > FALSE

【SQL】3値論理とは - okinawa

バッファプールとログバッファ

  • バッファプール:ストレージ(HDD)にあるRDBのデータの一部を保持している。
  • ログバッファ:更新処理のときに更新情報をログバッファ上にためて、コミット時にまとめて行うための領域。

バッファプールとログバッファ - okinawa

豆知識

COUNT(*)とCOUNT(列名)の違い

  • COUNT(*):NULLも含む全行カウント
  • COUNT(列名):NULLではない行数カウント

ちなみにAVG()はNULLの行はカウントしないで計算する。

相関名(AS~)の有効範囲

()内の名前は()の内側のみで使用可能。
外側では使用不可。

これは実行順序の問題。
サブクエリは内側から実行される。
実行された後は実行結果しか残らないので名前はもう残っていない。。。
なんかかっこいい。

ほかにもSELECT句でつけたAS名がWHERE句で使えないのもSELECT句の方が実行順序が後だから。

UNIONの使い所

異なるテーブルをまとめて集計したいとき。
UNIONの使い所 - okinawa

CASE式のELSE句は必ず書くべし!

ELSEは省略するとNULLが入ってしまうので。
SQL CASE式について - okinawa

サブクエリの実行順序と相関サブクエリ

サブクエリは内側から実行される。
SQL サブクエリについてのメモ - okinawa

特性関数とは

ある値が集合に含まれるか調べる関数。
集合に対する条件判定や集計に便利な関数です。
【SQL】特性関数とは - okinawa

SQLチューニング

実行計画の見方。

  • MySQLSQL文の頭に explain を付けるだけ。
  • SQLServerSQL文の頭に SET STATISTICS PROFILE ON を付ける。

GUIで見る方法もある↓

実際の実行プランの表示 - SQL Server | Microsoft Learn

チューニングについては過去記事も参照↓

GMOのDBチューニング課題やってみた - okinawa

インデックスって何なの?

DB勉強メモ - okinawa

SQLチューニング方法一覧

SQLチューニングの一覧 - okinawa

統計情報とは

実行計画を練るためのネタ元。
統計情報を更新するだけでパフォーマンス改善する場合もあり。

統計情報とは - okinawa

一時テーブル

大量データを持つテーブルを何回も読み込むとパフォーマンスが悪化する。

そのため一部データを取り出して、一時テーブルを作り、一時テーブルを読み込む。

一時テーブルに主キーを設定してやると尚良し。

テーブル結合関連

inner join と outer joinの違いはベン図で表すとわかりやすい

ベン図

・参考記事
SQL Joinサンプル集 Joinで遅いSQLの原因を調べる方法 | ポテパンスタイル

Isn't SQL A left join B, just A? - Stack Overflow

on句が一部合致するとどうなるの?

下記の2テーブルをleft outer joinで実験。

Shohinテーブル

Zaikoテーブル

select * from Shohin
left outer join Zaiko
on Zaiko.name = Shohin.shohin_mei
and Zaiko.category = Shohin.shohin_bunrui;

・結果

on句が全部合致した行だけ値が入って結合されている。1行目と3行目。

on句が一部合致した行は全部nullで結合されている。4行目。

すべて合致した行のみ結合され、それ以外はNULLで出力されるということ。

結果

on句をwhere句っぽく使うときの注意点

・torihikijiyuテーブル

取引事由テーブル

・haishi_kouzaテーブル

廃止口座テーブル

SQL文(right outer join)

select * from haishi_kouza
right outer join
torihikijiyu as tor
on
tor.torihikijiyumei = '契約' --where句っぽいところ

結合後2

上記のように取引事由が「契約」以外の行も出力されてしまう。
right joinの場合は右側のテーブルは結合条件に合致しなくても全て出力される。
そのためtorihikijiyuテーブルの行はすべて出力。

この場合は、leftかinner joinにすれば取引事由が「契約」の行のみ出力される。

あれ?どゆこと? where句と同じことになると思ってたけどそうじゃないようで。
どうやらouter側のテーブルは全部返ってくる。
でも inner joinの時はwhere句みたいな動きをしている。(SQL1)
ま、where句みたいに使いたいならwhere句を使いましょう。
→where句よりjoin句で絞り込んだほうが処理が早いらしい。

SQL文1(left join)

select * from haishi_kouza
left join
torihikijiyu as tor
on
tor.torihikijiyumei = '契約'

結合後

結果は廃止口座テーブルの全ての行に取引事由テーブルの「契約」の行だけが結合される。

ここのまとめ

結合の向きに注意する。

・tableA left join tableB
tableB.columnB = '契約' にすると「契約」の行のみ結合して出力。

・tableA right join tableB
tableB.columnB = '契約' にすると「契約」以外の行も全て出力されてしまう。

テーブル結合ってなんなの?(重要)

無条件テーブル結合をやってみたらわかりやすかった。

・torihikijiyuテーブル

取引事由テーブル

・haishi_kouzaテーブル

廃止口座テーブル

SQL(無条件結合)

select * from haishi_kouza
inner join
torihikijiyu as tor
on
1 = 1

・結果

結合後

テーブル結合ってのは
列は、tableA + tableB
行は、tableA × tableB
になるんだなあ。

外部結合ミスの見つけ方

外部結合が上手くいってなくて不要な行が出力されたり、nulで出力されて困った時に。

  • nullではないはずのカラムがnullだったら外部結合ミスを怪しもう。ON句の結合ができない行はnullを含んで出力される。
  • 妙なところにMAXやMINが使われていたら外部結合ミスを取り繕っている可能性あり

テーブル結合で行を増殖させないポイント

ON句の条件を

  • 1対1で結合
  • 1対多で結合

【SQL】テーブル結合で行を増殖させないポイント - okinawa

集合演算をテーブル結合でやってみる

SQLの集合演算をテーブル結合でやってみる

テーブル結合ハマったこと

テーブル結合でハマったこと - okinawa

論理と集合

分配法則

掛け算と同じ。

A & (B or C) == (A & B) or (A & C)

A or (B & C) == (A or B) & (A or C)

ドモルガンの法則

!(A & B) == !A or !B

!(A or B) == !A & !B

対偶とごっちゃになるので注意。

SQLを読むときのポイント

  • 最初にSELECT行を見て何を取得するか把握する
  • 最初のFrom句のテーブルがメインテーブル
  • group byやsumなどの集約関数を見ると何を集計したいのかわかる
  • ネストの深いところから見ていくと割とわかりやすい

SQLテストサイト

ブラウザでSQLを実行できる便利サイト↓
sqlfiddle.com

その他

DISTINCTとNOT EXISTSでハマった話

NOT EXISTSとDISTINCTでハマった話 - okinawa

テスト駆動開発の参考サイト

テスト駆動開発のお勉強をしたときに参考にしたサイト集。

自動テスト。

Junit5全体

qiita.com

公式(日本語)
oohira.github.io

公式(英語)
junit.org

mockとspy

mockとspyの書き方
rightcode.co.jp

mockとspyの違い
www.shookuro.com

privateメソッドのテスト

qiita.com

ライブコーディング

www.youtube.com

メモ

便利アノテーション

@ParameterizedTestは複数の値を1度でテストできて便利。

テストクラス自動作成ショートカット

VsCode
Ctrl + Shift + P → Java Go to Test

Eclipse
Ctrl + F9

仕様からTODOリストを作る

TODOリストを作ってからテストコードを書く。

FizzBussの仕様

引用元:TDD Boot Camp 2020 Online #1 基調講演/ライブコーディング - YouTube

TODOリスト

テスト駆動開発のサイクル


引用元:TDD Boot Camp 2020 Online #1 基調講演/ライブコーディング - YouTube

テストコードは準備・実行・検証で書く

テストコードは

  • 準備
  • 実行
  • 検証

に分けて書く。

最初に検証から書く。

準備・実行はその後。

MySQL Shellでログイン

ログイン

\connect root@ホスト名 or IPアドレス:ポート番号

\connect root@localhost:3306
もしくは
\connect root@127.0.0.1:3306

↓ パスワードを聞かれる

Creating a session to 'root@127.0.0.1:3306'
Please provide the password for 'root@127.0.0.1:3306':*******

↓ ログイン成功

 MySQL  127.0.0.1:3306 ssl  JS >

実際のコンソール

JSモード・SQLモード

\js JavaScript実行モードに切り替え。
\sql SQL実行モードに切り替え。

用途

Dockerコンテナ内のMySQLに接続するのに使ってみた。

参考

docs.oracle.com

DockerでReact環境構築(Dev Containerでホットリロード)

自分用メモ。

Dev Containerの詳しいことは下記に書いたのでこちらはメモ程度。
この記事は下記の続き。
dodosu.hatenablog.jp

前提

下記は準備済み。

  • VsCode
  • Docker DeskTop
  • WSL2
  • Reactプロジェクト

docker-compose.yml作成

version: "3"

services:
  react-app: 
    image: node
    volumes: #ローカルのソースコードのパス:コンテナのソースコードのパス
      - C:\VsCode_Workspace\weather_app\frontend:/react-app
    command: npm start # これ効いてないかも
    ports:
      - "3000:3000"

Dev Containerでコンテナ起動

  1. VsCodeでdocker-compose.ymlを配置したフォルダを開く
  2. Ctrl + Shift + P
  3. Dev Containers: Add Dev Container Configration file
  4. Add Configration to workspace
  5. From docker-compose.yml
  6. OK
  7. Reopen in Container

これでコンテナ起動&コンテナ内VsCodeが開く。

terminalでnpm startして動作確認

DockerでSpringBoot環境構築(デバッグ実行と自動再起動あり)

前提

下記は準備済み。

  • VsCode
  • Docker DeskTop
  • WSL2
  • SpringBootプロジェクト

やりたいこと

  • Dockerコンテナ内にSpringBootプロジェクトを配置
  • ローカル環境と同じようにデバッグ実行可能
  • コード直したら自動再起動もしたい

※自動再起動はSpring Boot Devtoolsの機能。

pom.xmlに以下を記述。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
</dependency>

JDKはどのイメージを使う?

DockerHubをみるとOpenJDKが非推奨になっていた。

下記サイトを参考にeclipse-temurinに決定。

zenn.dev

まずは空のJava環境を作る

docker run --name test -dit eclipse-temurin

docker desktopで中身を入るとUbutu22の中にJDK21をインストールしてあるようで。

eclipse-temurinのDockerfile↓

eclipse-temurinのDockerfile

ローカルのソースをコンテナ内と同期させる

ローカルでソースを編集したら、コンテナ内のソースも同期させたい。

コンテナ内にソースを置くworkspceを作ろうと思ったが、どこが良いかわからなかったので参照したサイト↓
qiita.com

ルートディレクトリにworkspaceディレクトリを作ることにした。

ここにローカルのソースコードを同期させる。

# docker run --name コンテナ名 -v ローカルのパス:コンテナ内のパス -dit イメージ名
docker run --name test -v C:\VsCode_Workspace\weather_app\backend:/workspace  -dit eclipse-temurin

Dockerデスクトップで見るとworkspceディレクトリが作られている。

ローカル側でworspce内にファイルを作ってみる。

コンテナ内にもnewfile.txtができている。同期OK。

コンテナ内でどうやって起動するの?

mvn clean→mnv installでjarファイルを作成→実行、という流れで起動できる。

以下に詳しい手順。

VsCodeのターミナルで.mvnファイルがある場所に移動する。

VsCodeのサイドバーでmaven clean → maven install

targetフォルダにjarファイルができる

コンテナ内のtargeディレクトリまで移動してjava -jar jarファイル名で実行。起動OK。

しかし、これだとデバッグ実行ができないし、手動で毎回起動するのはしんどい。

デバッグ実行&ソース変更したら自動再起動もしたい

ここが大変だった。

大きく2つの方法があるみたい。

方法1:デバッグ実行中のプロセスにattachする

Dockerfileでデバッグ実行&launch.jsonデバッグ実行中のプロセスにattachする。

一応ChatGPTに聞いた内容を記載する。未検証なので動かないかも。

・Dockerfile

# ベースとなるDockerイメージを指定します
FROM adoptopenjdk/openjdk11:alpine

# アプリケーションのJARファイルをコピーします
COPY target/my-spring-boot-app.jar /app/my-spring-boot-app.jar

# デバッグポートを開放します(デフォルトは5005ポート)
EXPOSE 5005

# コンテナ内での作業ディレクトリを指定します
WORKDIR /app

# Dockerコンテナが起動する際に実行されるコマンドを指定します
# -agentlib:jdwp~がデバッグ実行らしい
CMD ["java", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005", "-jar", "my-spring-boot-app.jar"]

・launch.json

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "java",
      "name": "Debug (Attach) - Docker",
      "request": "attach", // launchだと新規起動。attachだと実行中プロセスに接続
      "hostName": "localhost",
      "port": 5005
    }
  ]
}

方法2:Dev Container(VsCode拡張機能

Dev Containerとは大雑把に言えば、VsCodeごとコンテナ内に環境を移植するもの。

引用元:https://code.visualstudio.com/docs/devcontainers/containers

■特徴
ローカルのVsCodeでできることは、コンテナ内でも大体できる。

もちろんデバッグ実行やSpringBootの自動再起動も。

■手順
拡張機能Dev Containerをインストール。

docker-compose.ymlを作成。
中身は特に変わったことはなく、eclipse-temurinイメージでコンテナ作成し、ローカルのソースとコンテナ内のソースを同期させる。

version: '3'
services:
  javatest:
    image: eclipse-temurin
    # ローカルのソースコードをコンテナ内と同期
    volumes:
      - C:\VsCode_Workspace\weather_app\backend:/workspace
    # コンテナ起動状態を維持する
    tty: true

コンテナ起動

docker compose -f ymlのパス  up -d

先程作ったdocker-compose.ymlを配置したフォルダをVsCodeで開く

VsCodeでCtrl + Shift + P → Dev containers Attach Dev container configuration filesを選択

どっちでもOK。上はコンテナの変更をローカルに反映しない。下はする。

今回はdocker-compose.ymlを選択。

拡張機能。今回はなしでOK。

Reopen in Container。初回はかなり時間かかる。

コンテナ内の環境でVsCodeが開く。

コンテナ内のVsCode拡張機能Extension Pack for Javaをインストール

ローカル環境の時と同じように、F5押してデバッグ実行。成功!

ローカルとコンテナ内に.devcontainerというフォルダが作られている。
その中にあるdevcontainer.jsonがDev Containerの設定ファイル。
拡張機能などを設定できる。

詳細は下記を参照。
qiita.com

公式
containers.dev

拡張機能を毎回インストールするのは面倒

devcontainer.jsonに欲しい拡張機能を記述すると、コンテナ構築時にインストールしてくれる。

拡張機能の歯車マークをクリックして「Add a devcontainer.json」を選択すると追加される。

・devcontainer.json

{
    "customizations": {
        "vscode": {
            "settings": {},

            "extensions": [
                "vscjava.vscode-java-pack",
                "vmware.vscode-boot-dev-pack"
            ]
        }
    }
}

続き(ReactをDockerで環境構築)

dodosu.hatenablog.jp

React・Spring Boot・MySQL・VSCode環境構築

前提

はインストール済み。

Spring Boot

・参考

qiita.com

1,拡張機能のインストール

  • Java Extension Pack
  • Spring Boot Extension Pack

2,Spring Boot新規プロジェクト作成

Ctrl * Shift + P → Spring Initializerで新規プロジェクト作成する。

spring initilizer

■追加したDependency

  • Spring Boot DevTools
  • Lombok
  • Spring Web
  • MySQL Driver
  • Mybatis(あとで追加する。動作確認めんどくなるので)

ここまでいったらF5で一旦起動してみる。

3,Hello Worldする

下記のような、Sample Controllerを追加。http://localhost:8080/にアクセスする。

package com.example.backend.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.backend.entity.TestScore;
import com.example.backend.mapper.SampleMapper;

@RestController
public class SampleController {
    @Autowired
    SampleMapper sampleMapper;

    // @CrossOrigin(origins = "http://localhost:3000")
    @RequestMapping("/")
    public String test() {
        return "Hello World!";
        // List<TestScore> result = sampleMapper.select();
        // return result.toString();
    }
}

MySQL

・参考
qiita.com

qiita.com

1,pom.xmlにMyBatisを追加

     <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>3.0.3</version>
        </dependency>

2,application.propertiesを編集

# spring.application.name=backend
spring.datasource.url=jdbc:mysql://localhost:3306/testDB
spring.datasource.username=*****
spring.datasource.password=*****
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.sql.init.mode=always

3,mapper.xml、mapperInterfaceとentityを追加

xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.backend.mapper.SampleMapper">
    <select id="select" resultType="com.example.backend.entity.TestScore">
        SELECT * FROM test.testscore;
    </select>
</mapper>

・Interface

package com.example.backend.mapper;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;
import com.example.backend.entity.TestScore;

@Mapper
public interface SampleMapper {
    List<TestScore> select();
}

・entity

package com.example.backend.entity;

import lombok.Data;

@Data
public class TestScore {
    private int id;
    private int studentid;
    private String subject;
    private int score;
}

4,ControllerでSelect結果を表示

package com.example.backend.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.backend.entity.TestScore;
import com.example.backend.mapper.SampleMapper;

@RestController
public class SampleController {
    @Autowired
    SampleMapper sampleMapper;

    // @CrossOrigin(origins = "http://localhost:3000")
    @RequestMapping("/")
    public String test() {
        List<TestScore> result = sampleMapper.select();
        return result.toString();
    }
}

http://localhost:8080。接続成功!

React

・参考
qiita.com

0,React+Redux+TypeScriptをコマンド1発で作成(今回は使わず)

npx create-react-app my-app --template redux-typescript

cd my-app

npm start //サーバー起動

1,Reac+TypeScriptで新規プロジェクト作成

今回は勉強のため、1発コマンドは使わず、StoreやReducerを自分で書く。

npx create-react-app my-app --template typescript

cd my-app

npm install @reduxjs/toolkit
npm install react-redux
npm install axios //APIとの通信に使うライブラリ

npm start //サーバー起動

起動確認(http://localhost:3000)

ReactからSpring Bootに接続(CORS設定)

・参考
qiita.com

zenn.dev

1,App.tsxを編集

import React from 'react';
import logo from './logo.svg';
import './App.css';
import { useEffect } from 'react';
import axios from 'axios';

function App() {
  // Spring Bootに接続(編集点ここだけ)
  useEffect(() => {
    axios.get('http://localhost:8080').then((res) => {
      console.log(res.data);
    });
  })

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.tsx</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
      <p>aaa</p>
    </div>
  );
}

export default App;

2,http://localhost:3000に接続

CORS設定しないとコンソールにエラー出るはず。

Access to XMLHttpRequest at 'http://localhost:8080/' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

console

3,CORS設定

・WebConfig.java

アプリケーション全体にCORS設定をするときは、下記のようにaddCorsMappingsをオーバーライドする。

各コントローラー毎にCORS設定する時は@CrossOriginを使う。

package com.example.backend.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // CORS設定
        registry.addMapping("/**").allowedOrigins("http://localhost:3000");
    }
}

consoleにSelect結果が表示!接続成功!

Redux

APIとの接続をReduxのcreateAsyncThunk使って書き換える。

・参考
dodosu.hatenablog.jp

・Store(store.ts)

まずはStoreを作成。

import { useDispatch, useSelector } from 'react-redux'
import type { TypedUseSelectorHook } from 'react-redux'


//Store生成
const store = configureStore({
    reducer: {
        // ここにReducerを登録する
        sample: sampleSlice.reducer,
    }
});

// TypeScriptの場合は下記をやらずに素のuseSelectorを使うと「does not exist on type ~」エラー出る。
// 参考:https://redux-toolkit.js.org/tutorials/typescript#define-typed-hooks
// 型推論&型エイリアス宣言。
// RootStateという型エイリアスを宣言
// store.getStateでstore自身からStateを取得して型推論。具体的にはsampleSliceのState型になる。
type RootState = ReturnType<typeof store.getState>
type AppDispatch = typeof store.dispatch

// カスタムフック定義&型定義
// 素の `useDispatch`をuseAppDispatchという名前にしてカスタムフック定義、戻り値の型をAppDispatchに定義。
export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector //「const useAppSelector = useSelector」に型定義TypedUseSelectorHook<RootState>を加えているだけ
export default store;

・Slice&Reducer(reducer.ts)

import { createSlice } from "@reduxjs/toolkit";
import { getRouteApi } from "../connection/connectApi";

export const sampleSlice = createSlice({
    name: 'sample',
    initialState: {
        status: '',
        result: ''
    },
    // ノーマルReducerの登録場所(同期処理)
    reducers: {},
    // extraReducerの登録場所(非同期処理)
    extraReducers: (builder) => {
        builder.addCase(getRouteApi.pending, (state) => {
            state.status = 'Loading';
        })
        .addCase(getRouteApi.fulfilled, (state, action) => {
            state.status = 'Finished';
            state.result = action.payload;
        })
        .addCase(getRouteApi.rejected, (state) => {
            state.status = 'Failed';
        })
    }
})

・createAsyncThunk(connectApi.ts) APIとの接続処理。

import axios from 'axios';
import { createAsyncThunk } from '@reduxjs/toolkit';

const getRouteApiFunc = (): Promise<string> => {
    return axios.get('http://localhost:8080')
    .then((res) => {
        console.log(res.data);
        return res.data;
    })
    .catch((error) => {
        console.log(error);
    })
};

//createAsyncThunk(第1引数:文字列, 第2引数:Promiseを返す非同期関数)
export const getRouteApi = createAsyncThunk(
    'route/getApi',
    getRouteApiFunc
  );

・Child.tsx
Appコンポーネントの子コンポーネント

import { useAppDispatch, useAppSelector } from "../store/store";
import { getRouteApi } from '../connection/connectApi';

const Child = () => {
    const status = useAppSelector((state) => state.sample.status);
    const result = useAppSelector((state) => state.sample.result);
    const dispatch = useAppDispatch();

    const getTestScore = () => {
        dispatch(getRouteApi());
    }

    return(
      <>
        <p>{result}</p>
        <p>{status}</p>
        <button onClick={getTestScore}>sample</button>
      </>
    )
  }

export default Child;

・App.tsx

import './App.css';
import { Provider } from "react-redux";
import store from './store/store';
import Child from './component/Child';

function App() {

  return (
    <Provider store={store}>
      <div className="App">
        <Child />
      </div>
    </Provider> // ←これ忘れがち
  );
}

export default App;

sampleボタン押してDBから取得した値が表示されればOK!

結果画面

Spring Boot基礎&リンク集

基礎記事を書こうと思ったけど、すでに素晴らしい記事があったのでリンク集にする。

※リンクを上から見ていけばわかるようにすること。

Spring Boot全体の参考サイト

stechup.co.jp

stechup.co.jp

stechup.co.jp

stechup.co.jp

さらに踏み込んだ内容↓
www.youtube.com

Dependency Injection(依存性の注入)の参考サイト

www.youtube.com

tech-blog.yayoi-kk.co.jp

qiita.com

アノテーション

annotationは注釈という意味。

  • @Controller
  • @Service
  • @Repository

これらのよく見るアノテーション

Bean化して、DIコンテナに入れるためのアノテーション

実は@Componetで代用可能。

役割を明示するために@Serviceなどしているが、全部@Componetにしても機能はする?→一度試してみたい。

DIコンテナから呼び出すには?

@Autowiredで呼び出すだけ。

@Repository //Bean化してDIコンテナに格納
public interface SampleRepository {  }

@Service //Bean化してDIコンテナに格納
public class SampleService {  

  @Autowired //DIコンテナから呼び出し
  SampleRepository sampleRepository;
}

@RepositoryでBeanとしてDIコンテナに格納 → @Autowiredが付与されたクラスにインスタンスが注入される

Beanとは

Javaで言うところのBeanとは再利用可能なデータのこと。

getter stterメソッドが用意されているクラス。

Dependency Injection(依存性の注入)とは

■DIの概要
DIコンテナ=Beanをインスタンス化したものの入れ物

@ComponentなどでDIコンテナにBeanを格納する

DIコンテナからBeanインスタンス@Autowiredで取り出す

■DIのメリット

  1. DIではインスタンスがシングルトンで1個しか作られないので、メモリ節約できる。
  2. 疎結合なためテストがしやすい。

DIしない場合とDIする場合で見比べてみる↓

・DIしないコード例

public class CarController {
 
  public Car car() {
    Device device = new Device();
    device.engineOn();
  }
}

上記のコードだと、carメソッドが呼ばれる度にDeviceクラスのインスタンスが作られる。1万回リクエスト来たら1万個のインスタンスになる。

CarControllerクラスをテストするときに、Deviceクラスが未完成だとテストできない。

・DIするコード例

public class CarController {
  @Autowired
  DeviceService devideService;
 
  public boolean car() {
    boolean isEngineOn = devideService.engineOn();
    return isEngineOn;
  }
}

@Service
public class DeviceImpl implements DeviceService {
  
  public boolean ebginOn() {
    if (xxx) {
      return true;
    else {
      return false;
  }
}

DIするとDeviceServiceインタンスは1個しか作られないため、メモリ節約できる。

また、テストのときにDeviceImpl クラスが未完成だったとしても、Mockクラスを作ればテスト可能。