forget for get

覚えるために忘れる

SpringBoot入門:DBから値を取得

気軽に試せるH2データベースを使えるようにする

src/main/resources/
application.properties

spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

spring.sql.init.encoding=UTF-8
spring.sql.init.mode=always
spring.sql.init.schema-locations=classpath:schema.sql
spring.sql.init.data-locations=classpath:data.sql

spring.h2.console.enabled=true

 

スキーマとテストデータのSQL

schema.sql

create table if not exists employee(
  id varchar(50) primary key,
  name varchar(50),
  age int
);

 

data.sql

insert into employee(id, name, age)
values('1', 'Tom', 30);

Model、Repository、Service、Controller、Viewを作成、修正

src/main/java/com.example.demo/hello
Employee.java

@Data
public class Employee {
	private String employeeId;
	private String employeeName;
	private int employeeAge;
}

HelloRepository.java

@Repository
public class HelloRepository {
	@Autowired
	private JdbcTemplate jdbcTemplate;
	
	public Map<String, Object> findById(String id) {
		String query = "select *"
				+ " from employee"
				+ " where id=?";		
		Map<String, Object> employee = jdbcTemplate.queryForMap(query, id);		
		return employee;
	}
}

HelloService.java

@Service
public class HelloService {
	@Autowired
	private HelloRepository repository;
	
	public Employee getEmployee(String id) {
		Map<String, Object> map = repository.findById(id);
		
		String employeeId = (String) map.get("id");
		String name = (String) map.get("name");
		int age = (Integer) map.get("age");
		
		Employee employee = new Employee();
		employee.setEmployeeId(employeeId);
		employee.setEmployeeName(name);
		employee.setEmployeeAge(age);
		
		return employee;
	}
}

HelloController.javaに追記

	@Autowired
	private HelloService service;
	
	@PostMapping("/hello/db")
	public String postDbRequest(@RequestParam("eid") String id, Model model) {
		Employee employee = service.getEmployee(id);
		model.addAttribute("employee", employee);
		return "hello/db";
	}

src/main/resources/templates
db.htmlを作成

        <p th:text="${employee.employeeId}"></p>
        <p th:text="${employee.employeeName}"></p>
        <p th:text="${employee.employeeAge}"></p>

 

hello.htmlに追記

        <form method="post" action="/hello/db">
            <input type="text" name="eid" th:value="${eid_value}">
            <button>送信</button>
        </form>

 

H2データベースのコンソール
http://localhost:8080/h2-console
JDBC URL: jdbc:h2:mem:testdb

 

 

 

 

SpringBoot入門:環境構築

参考書はこちらを選択(Kindle Unlimitedで無料で読めるので)
Spring 解体新書

 

環境構築


IDE
Spring Tools 4 for Eclipse
https://spring.io/tools/
windows版をダウンロード、任意の場所に配置して「java -jar spring-tool-suite-xxx.jar」
sts-x.x.x.RELEASEの中のSpringToolSuite4.exeが本体

 

Pleiadesの日本語化プラグイン
https://willbrains.jp/
Pleiades プラグイン・ダウンロードからWindowsをダウンロード
解凍してsetup.exe
先ほどのSpringToolSuite4.exeを選択して日本語化

 

Lombokインストール
https://projectlombok.org/download
java -jar lombok.jar
SpringToolSuite4.exeを選択してインストール

 

Spring Tools 4の設定
コード補完の設定
ウィンドウ→設定→Java→エディタ→コンテンツアシスタント
Javaの自動有効化トリガー
.abcdefghijkemnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_
※デフォルトだと.を入力した時しか補完されない
「Enter以外の挿入トリガーを使用不可にする」にチェック

Ctrl + Shift + Fでコード整形

 

新規Springスタータープロジェクトの作成
追加ライブラリ
Spring Boot DevTools, Lombok, JDBC API, Spring Data JDBC, H2 Database, Thymeleaf, Spring Web

 

src/main/resources/templates
hello.htmlを作成

<html xmlns:th="http://www.thymeleaf.org">
    <body>
        Hello World
    </body>
</html>

 

src/main/java/com.example.demo/hello
HelloController.javaを作成

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HelloController {
	@GetMapping("/hello")
	public String getHello() {
		return "hello";
	}
}

プロジェクトを右クリック→実行→SpringBootアプリケーション
http://localhost:8080/hello

Java入門

筆者はPHPを習得しているので環境構築くらいしか書くことがない。

(言語の違いで気づいたことがあれば追記していく)

 

JavaDriveでJava入門
https://www.javadrive.jp/start/

 

JDKのインストール
https://jdk.java.net/
Ready for use: JDK XXをクリック
BuildsからWindowsのzipをダウンロード
任意のディレクトリに展開

 

環境変数の設定
検索窓に「環境変数」と入力して環境変数Windowを開く
ユーザー環境変数かシステム環境変数に「JAVA_HOME」を追加
変数値には先ほど配置したパスを指定「C:\xxx\jdk-xx.x.x」
「Path」の編集で、新規→「%JAVA_HOME%\bin」を追加
コマンドプロンプトで「javac -version」で確認

 

公式ドキュメント
https://www.oracle.com/jp/java/technologies/documentation.html
該当バージョンのリンク日本語→左メニューのAPIドキュメント

 

Hello, World
Hello.java

class Hello {
  public static void main(String[] args) {
    System.out.println("Hello");
  }
}

 

コンパイル
javac Hello.java
実行
java Hello

 

サンプルプログラム

import java.time.LocalDate;

class Sample {
  public static void main(String[] args) {
  	final int MAX_LEVEL = 100;
  	
  	System.out.println(MAX_LEVEL);
  	
  	LocalDate ld = LocalDate.now();
    System.out.println(ld);
  	
  	//配列
  	int[] nums = new int[3];
  	nums[0] = 1;
  	nums[1] = 2;
  	nums[2] = 3;
  	
  	for (int i = 0; i < 3; i++) {
  		System.out.println(nums[i]);
  	}
  	
  	//こうも書ける
  	int[] nums2 = {1, 2, 3};
  	for (int num : nums2) {
        System.out.println(num);
    }
  	
  	//String型や配列型は参照型なので、代入すると同じものを参照する
  	int[] others = nums2;
  	nums2[0] = 4;
  	// 同じものを参照しているので、4になる
    System.out.println(others[0]);
  	
  	int[] others2 = nums2.clone();
  	nums2[0] = 5;
  	// cloneだと別オブジェクトになるので5にならない
    System.out.println(others2[0]);
  }
}

 

 

Compositeパターン

容器と中身の同一視。
ファイルシステムでは、ディレクトリとファイルが入れ子になっている。
ディレクトリとファイルは異なるものですが、「ディレクトリに入るもの」とみなしている。

Leaf(葉):MyFile
中身
Composite(複合体):MyDirectory
容器
Component:Entry
LeafとCompositeを同一視するためのスーパークラス
Client(依頼者):Main

他にも複数と単数の同一視など。

Entry.php

abstract class Entry {
  abstract function getName();
  abstract function getSize();
  function add(Entry $entry) {
    throw new Exception("FileTreatmentException");
  }
  abstract function printList($prefix=null);
  function toString() {
    return $this->getName() . "(" . $this->getSize() . ")";
  }
}

MyFile.php

class MyFile extends Entry {
    private $name;
    private $size;
    function __construct(String $name, int $size) {
        $this->name = $name;
        $this->size = $size;
    }
    function getName() {
        return $this->name;
    }
    function getSize() {
        return $this->size;
    }
    function printList($prefix=null) {
        echo $prefix . "/" . $this->toString() . "\n";
    }
}

MyDirectory.php

class MyDirectory extends Entry {
    private $name;
    private $directory = [];
    function __construct(String $name) {
        $this->name = $name;
    }
    function getName() {
        return $this->name;
    }
    function getSize() {
        $size = 0;
        foreach ($this->directory as $entry) {
            $size += $entry->getSize();
        }
        return $size;
    }
    function add(Entry $entry) {
        $this->directory[] = $entry;
        return $this;
    }
    function printList($prefix=null) {
        echo $prefix . "/" . $this->toString() . "\n";
        foreach ($this->directory as $entry) {
            $entry->printList($prefix . "/" . $this->name);
        }
    }
}

Main.php

require "../autoload.php";
echo "Making root entries...\n";
$rootdir = new MyDirectory("root");
$bindir = new MyDirectory("bin");
$tmpdir = new MyDirectory("tmp");
$usrdir = new MyDirectory("usr");
$rootdir->getName();
$rootdir->add($bindir);
$rootdir->add($tmpdir);
$rootdir->add($usrdir);
$bindir->add(new MyFile("vi", 10000));
$bindir->add(new MyFile("latex", 20000));
$rootdir->printList();

 

Mementoパターン

状態を保存しておいて、復元できるようにしておく。
undo,redo,history,snapshot

復元するためにインスタンスの内部情報を公開すると、カプセル化の破壊になるので、
インタフェースを使い分ける。

Originator(作成者):Gamer
自分の現在の状態を保存したいときにMementoをつくる。
以前のMementoを渡されると、その状態に戻る。
Memento(記念品):Memento
Originatorの情報を持っているが、誰にでも公開はしない。
広いインタフェース→オブジェクトを元に戻すために内部情報を得られるメソッド。Originatorだけが使える。
狭いインタフェース→Caretakerに見せるもの。
Caretaker(世話をする人):Main
Originatorの状態を保存したいときに、Originatorに伝える。
OriginatorはMementoをCaretakerに渡す。
CaretakerはMementoの狭いインタフェースしか使えないので、Mementoの内部情報をいじることはできない。

Memento.php

class Memento {
    private $money;
    private $fruits = [];
    function __construct($money) {
        $this->money = $money;
    }
    function getMoney() {
        return $this->money;
    }
    function addFruit($fruit) {
        $this->fruits[] = $fruit;
    }
    function getFruits() {
        return $this->fruits;
    }
}

Gamer.php

class Gamer {
    private $money;
    private $fruits = [];
    private $fruitNames = ["夕張メロン","すいか","もも","なし"];
    function __construct($money) {
        $this->money = $money;
    }
    function getMoney() {
        return $this->money;
    }
    function bet() {
        $dice = rand(1, 6);
        if ($dice == 1) {
            $this->money += 100;
            echo "所持金が増えました\n";
        } else if ($dice == 2) {
            $this->money /= 2;
            echo "所持金が半分になりました\n";
        } else if ($dice == 6) {
            $f = $this->getFruit();
            echo "フルーツ(" . $f . ")をもらいました\n";
            $this->fruits[] = $f;
        } else {
            echo "何も起こりませんでした\n";
        }
    }
    function createMemento() {
        $m = new Memento($this->money);
        foreach ($this->fruits as $f) {
            if (strpos($f, "おいしい") !== false) {
                $m->addFruit($f);
            }
        }
        return $m;
    }
    function restoreMemento(Memento $memento) {
        $this->money = $memento->getMoney();
        $this->fruits = $memento->getFruits();
    }
    function toString() {
        return "[money = " . $this->money 
        . ", fruits = " . implode(",", $this->fruits) . "]";
    }
    private function getFruit() {
        $prefix = rand(0, 1) ? "おいしい" : "";
        return $prefix . $this->fruitNames[rand(0, count($this->fruitNames) - 1)];
    }
}

Main.php

require "../autoload.php";
$gamer = new Gamer(100);
$memento = $gamer->createMemento();
for ($i=1;$i<=100;$i++) {
    echo $i . ":" . $gamer->toString() . "\n";
    $gamer->bet();
    echo "所持金:" . $gamer->getMoney() . "\n";
    if ($gamer->getMoney() > $memento->getMoney()) {
        echo " (保存)\n";
        $memento = $gamer->createMemento();
    } else if ($gamer->getMoney() < $memento->getMoney() / 2) {
        echo " (復元)\n";
        $gamer->restoreMemento($memento);
    }
    sleep(1);
}

 

 

Strategyパターン

アルゴリズムをごっそり切り替える

Strategy(戦略):Strategy
インタフェースを規定
ConcreteStrategy(具体的戦略):WinningStrategy、ProbStrategy
Context(文脈):Player
Strategyを利用。ConcreteStrategyのインスタンスを保持。

Hand.php

class Hand {
  const GUU = 0;
  const CHO = 1;
  const PAA = 2;
  static $hand = [];
  static private $name = ["グー","チョキ","パー"];
  private $handvalue;
  static function initialize() {
    self::$hand = [new Hand(self::GUU), new Hand(self::CHO), new Hand(self::PAA)];
  }
  function __construct($handvalue) {
    $this->handvalue = $handvalue;
  }
  static function getHand($handvalue) {
    return self::$hand[$handvalue];
  }
  function isStrongerThan(Hand $h) {
    return $this->fight($h) == 1;
  }
  function isWeekerThan(Hand $h) {
    return $this->fight($h) == -1;
  }
  private function fight(Hand $h) {
    if ($this == $h) return 0;
    if (($this->handvalue + 1) % 3 == $h->handvalue) return 1;
    return -1;
  }
  function toString() {
    return self::$name[$this->handvalue];
  }
}

Strategy.php

interface Strategy {
  function nextHand(): Hand;
  function study(bool $win);
}

WinningStrategy.php

// 勝ったら同じ手を出す戦略
class WinningStrategy implements Strategy {
  private $won = false;
  private $prevHand;
  function nextHand(): Hand {
    if (!$this->won) $this->prevHand = Hand::getHand(rand(0, 2));
    return $this->prevHand;
  }
  function study(bool $win) {
    $this->won = $win;
  }
}

ProbStrategy.php

// 勝敗履歴から確率を計算する戦略
class ProbStrategy implements Strategy {
  private $prevHandValue = 0;
  private $currentHandValue = 0;
  private $history = [[1,1,1], [1,1,1], [1,1,1]];
  function nextHand(): Hand {
    $bet = rand(0, $this->getSum($this->currentHandValue));
    $handvalue = 0;
    if ($bet < $this->history[$this->currentHandValue][0]) {
      $handvalue = 0;
    } else if ($bet < $this->history[$this->currentHandValue][0] + $this->history[$this->currentHandValue][1]) {
      $handvalue = 1;
    } else {
      $handvalue = 2;
    }
    $this->prevHandValue = $this->currentHandValue;
    $this->currentHandValue = $handvalue;
    return Hand::getHand($handvalue);
  }
  private function getSum($hv) {
    $sum = 0;
    for ($i=0;$i<3;$i++) {
      $sum += $this->history[$hv][$i];
    }
    return $sum;
  }
  function study($win) {
    if ($win) {
      $this->history[$this->prevHandValue][$this->currentHandValue]++;
    } else {
      $this->history[$this->prevHandValue][($this->currentHandValue + 1) % 3]++;
      $this->history[$this->prevHandValue][($this->currentHandValue + 2) % 3]++;
    }
  }
}

Player.php

class Player {
  private $name;
  private $strategy;
  private $wincount = 0;
  private $losecount = 0;
  private $gamecount = 0;
  function __construct($name, $strategy) {
    $this->name = $name;
    $this->strategy = $strategy;
  }
  function nextHand() {
    return $this->strategy->nextHand();
  }
  function win() {
    $this->strategy->study(true);
    $this->wincount++;
    $this->gamecount++;
  }
  function lose() {
    $this->strategy->study(false);
    $this->losecount++;
    $this->gamecount++;
  }
  function even() {
    $this->gamecount++;
  }
  function toString() {
    return "[" . $this->name . ":" . $this->gamecount . " games, " . $this->wincount . " win, " . $this->losecount . " lose]";
  }
}

Main.php

require "../autoload.php";
Hand::initialize();
$player1 = new Player("Taro", new WinningStrategy());
$player2 = new Player("Jiro", new ProbStrategy());
for ($i=0;$i<10;$i++) {
  $nextHand1 = $player1->nextHand();
  $nextHand2 = $player2->nextHand();
  if ($nextHand1->isStrongerThan($nextHand2)) {
    echo "Winner:" . $player1->toString() . "\n";
    $player1->win();
    $player2->lose();
  } else if ($nextHand2->isStrongerThan($nextHand1)) {
    echo "Winner:" . $player2->toString() . "\n";
    $player2->win();
    $player1->lose();
  } else {
    echo "Even...\n";
    $player1->even();
    $player2->even();
  }
}
echo "Total result:\n";
echo $player1->toString() . "\n";
echo $player2->toString() . "\n";

 

 

Bridgeパターン

機能の階層と実装の階層を分ける

機能(メソッド)を追加したいとき、サブクラス(子クラス、派生クラス、拡張クラス)をつくる
さらに機能を追加する場合、さらに階層が深くなる

Something
  SomethingGood
    SomethingBetter

抽象クラスでインタフェース(API)を規定し、サブクラスで実装する。
部品として交換しやすいクラスになる。

AbstractClass
  ConcreteClass1
  ConcreteClass2

機能の階層と実装の階層が混在していると見通しが悪いので分ける。

Abstraction(抽象化):Display
機能クラス階層の最上位。
Implementorを保持し、そのメソッドを使って機能を記述。

RefinedAbstraction(改善した抽象化):CountDisplay
Abstractionに機能を追加したもの。

Implementor(実装者):DisplayImpl
実装クラス階層の最上位。
Abstractionのインタフェースを実装するためのメソッドを規定。

ConcreteImplementor(具体的な実装者):StringDisplayImpl
Implementorを実装。

2つの階層を$implが橋渡ししている。
分けておけば拡張するのが楽になる。

継承だとソースコードを書き換えないと変わらない。
Displayのように、委譲すると、引数で渡すものを変えれば変わる。

Display.php

class Display {
  private $impl;
  function __construct(DisplayImpl $impl) {
    $this->impl = $impl;
  }
  function open() {
    $this->impl->rawOpen();
  }
  function print() {
    $this->impl->rawPrint();
  }
  function close() {
    $this->impl->rawClose();
  }
  final function display() {
    $this->open();
    $this->print();
    $this->close();
  }
}

CountDisplay.php

class CountDisplay extends Display {
  function __construct(DisplayImpl $impl) {
    parent::__construct($impl);
  }
  function multiDisplay($times) {
    $this->open();
    for ($i=0;$i<$times;$i++) {
      $this->print();
    }
    $this->close();
  }
}

DisplayImpl.php

abstract class DisplayImpl {
  abstract function rawOpen();
  abstract function rawPrint();
  abstract function rawClose();
}

StringDisplayImpl.php

class StringDisplayImpl extends DisplayImpl {
  private $string;
  private $width;
  function __construct($string) {
    $this->string = $string;
    $this->width = strlen($string);
  }
  function rawOpen() {
    $this->printLine();
  }
  function rawPrint() {
    echo "|" . $this->string . "|\n";
  }
  function rawClose() {
    $this->printLine();
  }
  private function printLine() {
    echo "+";
    for ($i=0;$i<$this->width;$i++) {
      echo "-";
    }
    echo "+\n";
  }
}

Main.php

require "../autoload.php";
$d1 = new Display(new StringDisplayImpl("Hello"));
$d2 = new CountDisplay(new StringDisplayImpl("Good bye"));
$d3 = new CountDisplay(new StringDisplayImpl("Good night"));
$d1->display();
$d2->display();
$d3->display();
$d3->multiDisplay(3);