Gitの設定ファイル

以下のサイトを見ながら自分の言葉でまとめてみた。
Git - Git の設定

Gitの設定方法

下記のようにgitコマンドを使ってGitの設定を変更することが出来る。

git config [設定先] [設定項目] [設定値]


例えばユーザー名として「John Doe」を設定するなら、設定項目が「user.name」、設定値が「John Doe」となり、コマンドは以下のようになる。

git config --global user.name "John Doe"

これでグローバル域にユーザー名「John Doe」を設定したことになる。

あとは、設定先を切り替えることで使いやすい設定にカスタマイズすれば良い。

続いてその設定先についてまとめる。

3段構えの設定先

Gitの設定先は3段構えとなっている。

  • システム:インストールしたGit本体に対する設定
  • グローバル:ユーザー単位での設定
  • ローカル:リポジトリ単位での設定

上から順にスコープが狭くなる。

それぞれを掘り下げてみてみる。

システム

インストールしたGit本体に対する設定で、Linux系だと下記のファイルに設定内容が書き込まれる。

/etc/gitconfig

Windowsの場合はGitのインストールフォルダの下記のファイルに設定内容が書き込まれる。

[インストールフォルダ]/etc/gitconfig


gitコマンドはまずこのファイルに記載した設定を読み込む。

[設定先]に使用するパラメータは「--system」となり、以下のように設定する。

git config --system core.autocrlf true

グローバル

そのPCのユーザーごとの設定で、LinuxでもWindowsでもユーザーのホームディレクトの下記ファイルに設定内容が書き込まれる。

.gitconfig

gitコマンドはシステムで設定された内容に次いでこのファイルに記載した設定を読み込む。
重複した設定は上書きされる(はず。未確認)。

ユーザーごとの設定なので、先ほどの例に出したユーザー名「John Doe」の設定はここにしたほうが良い。

[設定先]に使用するパラメータは「--global」となり、ユーザー名以外を設定するなら以下のようになる。

git config --global user.email john@doe.com

ローカル

作業中のリポジトリのみの設定で、リポジトリフォルダの下記のファイルに設定内容が書き込まれる。

.git/config

gitコマンドはシステム、グローバルの順にで設定された内容を読み込み、最後にこのファイルに記載した設定を読み込む。

例えばメインで使用するユーザー名「John Doe」はグローバルに設定し、このリポジトリだけ異なるユーザー名「Jane Doe」で作業をするなら、その設定を下記のように行う。

git config --local user.name "Jane Doe"

ローカルだけは、パラメータ「--local」を省略することも可能。

まとめ

3つの設定をまとめると下記のようになる。

git config --system [システムへの設定]
git config --global [グローバルへの設定]
git config --local [ローカルへの設定]

設定できる項目や設定に関することについては冒頭のGitのサイトや下記コマンドで表示するマニュアルページに詳しくあるので、そちらを参照。

git config --help

GitBucket 3.2 を試す

Scalaで実装されたオープンソースGitHubクローン」のGitBucketのインストールが予想以上に簡単だったので、メモを残す。
今回入れるGitBucketの3.2。

GitBucketのサイトは下記の通り。
https://github.com/takezoe/gitbucket

事前準備

Scalaで実装されているので、Javaが入っていないとならない。
今回はJava8(Update31)で試した。
Javaのインストールフォルダのbinフォルダにパスを通しておきましょう)

続いてGitBucket本体のダウンロード。
GitBucketのwarファイルをGitBucketのサイトからダウンロードする。
GitBucket 3.2は下記URLでダウンロードできる。
https://github.com/takezoe/gitbucket/releases/download/3.2/gitbucket.war

準備に必要なのはこの2点。

GitBucketの実行

GitBucketの実行は下記のコマンドで行う。

java -jar gitbucket.war

これだけ。
GitBucketには組み込みWebサーバのJettyを使っているため、上記のようにJARファイルを実行するとGitBucketをデプロイ済みのWebサーバーが起動する。

GitBucketへアクセスする

ブラウザを起動して、起動したサーバーもしくはローカルホストのポート8080にアクセスする。

http://localhost:8080/

以下のようなログイン画面が表示されれば正常に起動したことになる。
f:id:nave_kazu:20150513010540p:plain

あとは初期ユーザroot(パスワードもroot)でログインして、ユーザ追加やグループの追加等の設定を行う。

その他

GitBucketのサイトにある通り、javaコマンドで起動する際にパラメータを与えることが出来る。

自分が真っ先に行ったのは「--gitbucket.home」の設定。
初期状態では実行ユーザのホームディレクトリにGitBucketの設定とリポジトリが作成される。

ホームディレクトリ以外をGitBucketの作業場所とするには、下記のように起動パラメータを与える。

java -jar gitbucket.war --gitbucket.home=D:\GitBucket

起動すると、D:\GitBucketに設定内容を保存したデータベースファイル(H2かな?)とリポジトリフォルダなどが作成される。

ここまでが初期設定。
さて、Git使ってみるぞ。

Spring Boot/第五回 Spring Bootで Web (一覧表示)

Spring BootでWebの画面を作ってみる。
まずは顧客一覧画面から。

Contrller

ブラウザからのリクエストを受け取ってビジネスロジック呼んでブラウザにレスポンスを返すのがコントローラの役目。
コントローラは以下のようになる。

package tools.springsample.springsample05.web;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import tools.springsample.springsample05.domain.Customer;
import tools.springsample.springsample05.service.CustomerService;

@Controller    // a
@RequestMapping("customer")    // b
public class CustomerContrller {
    @Autowired
    private CustomerService customerService;

    @RequestMapping(method=RequestMethod.GET)    // c
    public String list(Model model) {    // d
        List<Customer> customers = customerService.findAll();
        model.addAttribute("customers", customers);    // e
        return "customer/list";    // f
    }
}

a -> コントローラであることを記すアノテーション
b -> コントローラがリクエストを受け取るURLを「customer」と定義する。
c -> URLの「customer」にGETメソッドが送られたときに実行するメソッドであることを記すアノテーション
d -> 画面に値を渡すModelを引数に受け取る。
e -> ビジネスロジックの顧客一覧取得結果をモデルにセットする。
f -> 画面のテンプレート「customer/list.html」を呼び出す。

@Controllerアノテーションでコントローラであることを宣言して、@RequestMapping("customer")アノテーションで受け取るURLを定義して、@RequestMapping(method=RequestMethod.GET)アノテーションでHTTPメソッドの何を受け取って処理するメソッドかを宣言すると、そのメソッド(今回だとCustomerContrllerクラスのlistメソッド)が呼ばれる。
リクエスト処理(listメソッド)内では処理結果をモデルにセットして結果を表示するURLを戻り値で返せば、後のテンプレートへ処理結果を引き継げる。
今回は処理結果として、CustomerServiceで提供している顧客リスト取得メソッド(findAllメソッド)の結果をModelに「customers」という名前でセットした。

テンプレート

動的なページの作成に「Thymeleaf」(タイムリーフ)というテンプレートエンジンを使用する。
HTMLに「th:xxx」や「data-th:xxx」などのタグを入れるとThymeleafが適切な値に置き換えてくれる。

HTMLのテンプレートは下記のフォルダに置く。

src/main/resources/template

コントローラで「customer/list」と返しているので、対象のテンプレートは下記のファイルとして記述する。

src/main/resources/template/customer/list.html

まず普通のHTMLとして表示する画面を下記のようにする。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8"/>
  <title>顧客一覧</title>
  <style type="text/css">
    .list th {
      background-color: #cccccc;
      padding: 5px;
    }
    .list td {
      padding: 5px;
    }
  </style>
</head>
<body>
  <table class="list">
    <tr>
      <th>ID</th>
      <th>氏名</th>
      <th>アドレス</th>
    </tr>
    <tr>
      <td>xx</td>
      <td>name</td>
      <td>address</td>
    </tr>
  </table>
</body>
</html>

このHTMLをブラウザで開くと下記の通り。
f:id:nave_kazu:20150408010029j:plain

続いてHTMLにThymeleafで処理を書いて動的な結果を出力するように変える。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org/">    <!-- a -->
<head>
  <meta charset="UTF-8"/>
  <title>顧客一覧</title>
  <style type="text/css">
    .list th {
      background-color: #cccccc;
      padding: 5px;
    }
    .list td {
      padding: 5px;
    }
  </style>
</head>
<body>
  <table class="list">
    <tr>
      <th>ID</th>
      <th>氏名</th>
      <th>アドレス</th>
    </tr>
    <tr th:each="customer:${customers}">    <!-- b -->
      <td th:text="${customer.id}">xx</td>    <!-- c -->
      <td th:text="${customer.name}">name</td>    <!-- d -->
      <td th:text="${customer.email}">address</td>    <!-- e -->
    </tr>
  </table>
</body>
</html>

a -> Thymeleafの名前空間を宣言。
b -> コントローラでModelにセットしたリスト「customers」を取得して「th:each」で件数分ループさせる。
   ループ中の要素へアクセスするキーとして「customer」を指定する。
c -> 上で要素へのアクセスキー「customer」からIDを取得。
   customersはCustomerクラスのリストなので、Customerクラスのフィールドidのゲッターメソッド結果をTDタグの値として当てはめる。
d -> Customerクラスのフィールドnameのゲッターメソッド結果をTDタグの値として当てはめる。
e -> Customerクラスのフィールドemailのゲッターメソッド結果をTDタグの値として当てはめる。

Thymeleafの処理を追記したこのHTMLをブラウザで開くと下記の通り。
f:id:nave_kazu:20150408010113j:plain

追記前と変わらない。
つまりThymeleafの記述はブラウザには影響しない。
そのため、WebデザイナーとWebデベロッパーが分業して作業を進めることができる。
WebデザイナーがHTMLを書いて、そのあとWebデベロッパーがThymeleafの記述を追記する。
そのあとでもWebデザイナーはHTMLをブラウザで開いて表示内容を確認できるのでデザインを変更することもできる。

コンパイルして実行

以下のようにコンパイルする。

mvn package

続いて起動

mvn spring-boot:run

ブラウザで下記のアドレスにアクセスする。
http://localhost:8080/customer

するとデータベースの内容を読み込んで下記のように結果を表示する。
f:id:nave_kazu:20150408010152j:plain

まとめ

コントローラとテンプレートを作って動的な画面を表示してみた。
次回はフォームを使って更新系の画面を試してみる。

■ 参考文献 ■

この記事で参考にしたのは「はじめての Spring Boot」です。

Spring Boot/第四回 Spring Bootで REST(POST時のLocation設定)

RESTではPOSTでデータを新規作成した時は、そのデータにアクセスするURLをLocationヘッダーで返すのが一般的とのことで、それを試してみる。

新規にデータを登録して、オートインクリメントに設定したプライマリキー「ID」が「5」として登録できたら、以下のURLを返すということ。

api/customer/5

CustomerRestController の postCustomer メソッドを以下のように修正する。

    @RequestMapping(method=RequestMethod.POST)
    public ResponseEntity<Customer> postCustomer(@RequestBody Customer customer,
            UriComponentsBuilder uriBuilder) {    // a
        Customer createdCustomer = service.create(customer);    // b

        // Locationで設定するURLを作成する
        URI location = uriBuilder.path("api/customer/{id}")    // c
                        .buildAndExpand(createdCustomer.getId())    // d
                        .toUri();

        // レスポンスのHTTPヘッダー
        HttpHeaders headers = new HttpHeaders();
        headers.setLocation(location);    // e

        // レスポンス情報を作成
        return new ResponseEntity<>(
                createdCustomer, headers, HttpStatus.CREATED);    // f
    }

a -> URI作成用クラスを引数で受け取る。
b -> 顧客情報の登録。
c -> プレースホルダ付の作りたいURLを指定する。
d -> プレースホルダに登録したIDを当てはめる。
e -> HTTPヘッダーにLocationのURLを設定。
f -> HTTPボディに createdCustomer の内容、
   HTTPヘッダーに headers の内容、
   HTTPステータスに CREATED を指定。

ビルドして Spring Boot を起動して curl で以下のように登録をする。

curl http://localhost:8080/api/customer -v -X POST -H "Content-Type: application/json" -d "{\"name\":\"name04\", \"email\":\"name04@test\"}"

実行結果は下記の通り。

# curl http://localhost:8080/api/customer -v -X POST -H "Content-Type: application/json" -d "{\"name\":\"name04\", \"email\":\"name04@test\"}"
*   Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> POST /api/customer HTTP/1.1
> User-Agent: curl/7.41.0
> Host: localhost:8080
> Accept: */*
> Content-Type: application/json
> Content-Length: 40
>
* upload completely sent off: 40 out of 40 bytes
< HTTP/1.1 201 Created
< Server: Apache-Coyote/1.1
< Location: http://localhost:8080/api/customer/4
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Fri, 03 Apr 2015 16:09:41 GMT
<
{"id":4,"name":"name04","email":"name04@test"}* Connection #0 to host localhost left intact

レスポンスヘッダーに今までなかった下記が追加されている。

< Location: http://localhost:8080/api/customer/4

うまく出来たようだ。


ちなみに、プレースホルダを解決する buildAndExpand メソッドには Map を引数に指定もできる。

        // プレースホルダ解決用Map
        Map<String, Integer> map = new HashMap<>();
        map.put("id", createdCustomer.getId());

        // Locationで設定するURLを作成する
        URI location = uriBuilder.path("api/customer/{id}")    // c
//                        .buildAndExpand(createdCustomer.getId())    // d
                        .buildAndExpand(map)    // d
                        .toUri();

プレースホルダの「{id}」を Map のキーにして値を当てはめてくれる。

■ 参考文献 ■

この記事で参考にしたのは「はじめての Spring Boot」です。

Spring Boot/第四回 Spring Bootで REST

Spring Bootを使うと、RESTの実装が簡単に出来るので試してみる。
試しに簡単な顧客管理のシステムを実装する。

REST

REST について、詳しい説明は他のサイトを参照してほしいが、要は CRUD のそれぞれを HTTP メソッドで表現するWebサービスの考え方のこと。

CRUD HTTPメソッド
Create POST
Read GET
Update PUT
Delete DELETE

何に対する CURD 操作なのかは URL で表す。
たとえば /api/customer に対して GET を送ると「顧客情報の取得」といった具合。

今回は下記の実装を試してみる。

API HTTPメソッド リソースパス 正常時HTTPレスポンスステータス
顧客全件取得 GET /api/customer 200 OK
顧客一件取得 GET /api/customer/{id} 200 OK
顧客一件作成 POST /api/customer 201 CREATED
顧客一件更新 PUT /api/customer/{id} 200 OK
顧客一件削除 DELETE /api/customer/{id} 204 NO CONTENT

{id}の部分にユニークなIDを渡して特定の顧客を取得したり更新したり削除したりする。

Maven

pom.xmlに追加する依存関係は下記の通り。

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>  <!-- a -->
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>  <!-- b -->
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>  <!-- c -->
      <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
    </dependency>
    <dependency>  <!-- d -->
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.14.0</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>

a -> Spring BootでWebアプリを作成するための依存関係。
b -> Spring BootでJPAを扱うための依存関係。
c -> H2の依存関係。
d -> Lombokの依存関係。

Repository

階層の一番下。
Repositoryから実装する。

まずは顧客情報を入れるデータベースの設定から。
pom.xmlに書いた依存関係の通り、データベースはH2を使用する。
プロジェクトがあるフォルダから相対パスで testdb フォルダ内に H2 のデータベースを作成する。
src/main/resources/application.yml に下記を記載。

spring:
  datasource:
    driverClassName: org.h2.Driver
    url: jdbc:h2:file:testdb/testdb
    username: sa
    password:
  jpa:
    hibernate:
      ddl-auto: none

データソースの URL の通り、testdb フォルダ内に testdb というデータベースファイルを指定する。
JPA プロバイダは Hibernate を使用するので、その設定も併せて行う。

続いてテーブル定義。
DDL は下記の通りで src/main/resources/schema.sql に記載する。

create table if not exists customer (
    id int primary key auto_increment,
    name varchar(50) not null,
    email varchar(50) not null
);

id -> プライマリキー。オートインクリメントで一意の値を自動で割り当てる。
name -> 名前を格納。
email -> メールアドレスを格納。


Hibernate はエンティティクラスからテーブルを自動作成する機能があるが、application.yml の「ddl-auto: none」の通り作成は行わない。
そのかわり、Spring Boot の機能で schema.sqlDDL を書いて、そちらでテーブルを作ってもらう。

続いてエンティティ定義。
src/main/java/toolsspringsample/springsample04/domain/Customer.java に記載する。

package tools.springsample.springsample04.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity    // a
@Table(name="customer")    // b
@Data    // c
@AllArgsConstructor    // d
@NoArgsConstructor    // e
public class Customer {
    @Id    // f
    @GeneratedValue    // g
    private Integer id;

    @Column(nullable=false)    // h
    private String name;

    @Column(nullable=false)    // h
    private String email;
}

JPAの記述とLombokの記述が入り乱れているので注意が必要。

a -> JPA: エンティティクラスであることを記すアノテーション
b -> JPA: どのテーブルとマッピングするのか指定する。
c -> Lombok: すべてのフィールドに対するsetter/getterメソッド、equalsメソッド、hashCodeメソッド、toStringメソッドが生成される。
d -> Lombok: すべてのフィールドを初期化する引数を持つコンストラクタが生成される。
e -> Lombok: 引数なしのデフォルトコンストラクタが生成される。
f -> JPA: フィールドidがエンティティの主キーであることを表す。
g -> JPA: 自動採番する主キーであることを表す。
h -> JPA: テーブルに該当するカラムであることを表す。nullでの更新を抑制する。


最後にリポジトリ定義。
src/main/java/toolsspringsample/springsample04/repository/CustomerRepository.java に記載する。

package tools.springsample.springsample04.repository;

import tools.springsample.springsample04.domain.Customer;

import org.springframework.data.jpa.repository.JpaRepository;

public interface CustomerRepository
    extends JpaRepository<Customer, Integer> {    // a
}

a -> JpaRepositoryを拡張したインターフェースを定義。

まだ複雑な実装がないので、定義はとてもシンプル。
JpaRepositoryを拡張すると、実行時に実装クラスが自動生成される。
ジェネリクスで永続化対象のクラス「Customer」を指定し、キー項目の型を「Integer」と指定する。

Service

サービスはビジネスロジックを書くが、今回は処理らしい処理は書かずに下位層のリポジトリの呼び出しのみを行う。
src/main/java/toolsspringsample/springsample04/service/CustomerService.java に記載する。

package tools.springsample.springsample04.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import tools.springsample.springsample04.domain.Customer;
import tools.springsample.springsample04.repository.CustomerRepository;

@Service    // a
@Transactional    // b
public class CustomerService {
    @Autowired    // c
    CustomerRepository repository;    // d

    // 顧客全件取得
    public List<Customer> findAll() {    // e
        return repository.findAll();
    }

    // 顧客一件取得
    public Customer findOne(Integer id) {    // f
        return repository.findOne(id);
    }

    // 顧客一件作成
    public Customer create(Customer customer) {    // g
        return repository.save(customer);
    }

    // 顧客一件更新
    public Customer update(Customer customer) {    // h
        return repository.save(customer);
    }

    // 顧客一件削除
    public void delete(Integer id) {    // i
        repository.delete(id);
    }
}

a -> サービスクラスであることを記すアノテーション
b -> トランザクションを扱うクラスであることを記すアノテーション
   DIからの CustomerService クラスのメソッド呼び出し時に自動的にトランザクション処理を行う。
   つまり例外が発生したらロールバックし、発生しなかったらコミットする。
c -> DIコンテナからインスタンスを取得する。
d -> リポジトリクラスの参照。
e -> 顧客全件取得処理。
f -> 顧客一件取得処理。
g -> 顧客一件作成処理。
h -> 顧客一件更新処理。
i -> 顧客一件削除処理。

このように今回は下位層のリポジトリの呼び出しのみを行う。

RestController

RestController は、URLと処理をマッピングし、そこからサービスの呼び出しを行う。

src/main/java/toolsspringsample/springsample04/api/CustomerRestController.java に記載する。

package tools.springsample.springsample04.api;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import tools.springsample.springsample04.domain.Customer;
import tools.springsample.springsample04.service.CustomerService;

@RestController    // a
@RequestMapping("api/customer")    // b
public class CustomerRestController {
    @Autowired    // c
    CustomerService service;    // d

    // 顧客全件取得
    @RequestMapping(method=RequestMethod.GET)    // e
    public List<Customer> getCustomer() {
        return service.findAll();
    }

    // 顧客一件取得
    @RequestMapping(method=RequestMethod.GET, value="{id}")    // f
    public Customer getCustomer(@PathVariable Integer id) {
        return service.findOne(id);
    }

    // 顧客一件作成
    @RequestMapping(method=RequestMethod.POST)    // g
    @ResponseStatus(HttpStatus.CREATED)    // h
    public Customer postCustomer(@RequestBody Customer customer) {
        return service.create(customer);
    }

    // 顧客一件更新
    @RequestMapping(method=RequestMethod.PUT, value="{id}")    // i
    public Customer putCustomer(@PathVariable Integer id,
                                @RequestBody Customer customer) {
        customer.setId(id);
        return service.update(customer);
    }

    // 顧客一件削除
    @RequestMapping(method=RequestMethod.DELETE, value="{id}")    // j
    @ResponseStatus(HttpStatus.NO_CONTENT)    // k
    public void deleteCustomer(@PathVariable Integer id) {
        service.delete(id);
    }
}

a -> RESTコントローラクラスであることを記すアノテーション
b -> URL(api/customer)とマッピングさせることを定義。
c -> DIコンテナからインスタンスを取得する。
d -> サービスクラスの参照。
e -> 顧客全件取得処理を HTTP の GET メソッドマッピングする。
f -> 顧客一件取得処理を HTTP の GET メソッドマッピングする。
   URLの最後に付加したIDを取得し、@PathVariable でその URL の ID と引数をマッピングする。
g -> 顧客一件作成処理を HTTP の POST メソッドマッピングする。
   POST 時の HTTP ボディを @RequestBody で引数とマッピングする。
h -> 正常時のレスポンスコードを CREATED (201) とする
i -> 顧客一件更新処理を HTTP の PUT メソッドマッピングする。
   URLの最後に付加したIDを取得し、@PathVariable でその URL の ID と引数をマッピングする。
   PUT 時の HTTP ボディを @RequestBody で引数とマッピングする。
j -> 顧客一件削除処理を HTTP の DELETE メソッドマッピングする。
   URLの最後に付加したIDを取得し、@PathVariable でその URL の ID と引数をマッピングする。

テスト用HTTPクライアント

REST を試すのに各種 HTTP メソッドをブラウザから送信するのは大変なので、コマンドラインから動作する HTTP クライアントの「curl」を使用する。
http://www.paehl.com/open_source/?Welcome

ブラウザでページを表示し、左側の「CURL」からダウンロード。
ZIPを解凍してコマンドにパスを通して動作できるようにする。

コンパイルして実行

以下のようにコンパイルする。

mvn package

続いて起動

mvn spring-boot:run
顧客一件作成

まずはcurlを使って顧客一件作成。

curl http://localhost:8080/api/customer -v -X POST -H "Content-Type: application/json" -d "{\"name\":\"name01\", \"email\":\"name01@test\"}"

curl コマンドのすぐ後で指定する URL が RestController の @RequestMapping の通りであることを確認。
-X で HTTP メソッドの「POST」を送り、CustomerRestControllerクラス の postCustomer メソッドを呼ぶ。
-H でボディの内容をJSONで送ることを指定。
-d でボディの内容を記載。

実行結果は下記の通り。

# curl http://localhost:8080/api/customer -v -X POST -H "Content-Type: application/json" -d "{\"name\":\"name01\", \"email\":\"name01@test\"}"
*   Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> POST /api/customer HTTP/1.1
> User-Agent: curl/7.41.0
> Host: localhost:8080
> Accept: */*
> Content-Type: application/json
> Content-Length: 40
>
* upload completely sent off: 40 out of 40 bytes
< HTTP/1.1 201 Created
< Server: Apache-Coyote/1.1
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Thu, 02 Apr 2015 17:28:06 GMT
<
{"id":1,"name":"name01","email":"name01@test"}* Connection #0 to host localhost left intact

最後の行に返ってきた JSON が出力されている。
読みやすくすると以下の通り。

{
    "id":1,
    "name":"name01",
    "email":"name01@test"
}
顧客一件更新

続いて顧客一件更新。

curl http://localhost:8080/api/customer/1 -v -X PUT -H "Content-Type: application/json" -d "{\"name\":\"name01_\", \"email\":\"name01_@test\"}"

URLの最後に更新対象のID「1」を指定する。これは先ほど追加したID。
-X で HTTP メソッドの「PUT」を送り、CustomerRestControllerクラス の putCustomer メソッドを呼ぶ。
-H でボディの内容をJSONで送ることを指定。
-d でボディの内容を記載。

「name01」の部分を「name01_」に変更するリクエストになる。

実行結果は下記の通り。

# curl http://localhost:8080/api/customer/1 -v -X PUT -H "Content-Type: application/json" -d "{\"name\":\"name01_\", \"email\":\"name01_@test\"}"
*   Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> PUT /api/customer/1 HTTP/1.1
> User-Agent: curl/7.41.0
> Host: localhost:8080
> Accept: */*
> Content-Type: application/json
> Content-Length: 42
>
* upload completely sent off: 42 out of 42 bytes
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Fri, 03 Apr 2015 03:08:44 GMT
<
{"id":1,"name":"name01_","email":"name01_@test"}* Connection #0 to host localhost left intact

最後の行に返ってきた JSON が出力されている。
読みやすくすると以下の通り。

{
    "id":1,
    "name":"name01_",
    "email":"name01_@test"
}
顧客一件削除

続いて顧客一件削除。

curl http://localhost:8080/api/customer/1 -v -X DELETE

URLの最後に削除対象のID「1」を指定する。これは先ほど追加したID。
-X で HTTP メソッドの「DELETE」を送り、CustomerRestControllerクラス の deleteCustomer メソッドを呼ぶ。
今回はボディは不要なので -H も -d もなし。

実行結果は下記の通り。

# curl http://localhost:8080/api/customer/1 -v -X DELETE
*   Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> DELETE /api/customer/1 HTTP/1.1
> User-Agent: curl/7.41.0
> Host: localhost:8080
> Accept: */*
>
< HTTP/1.1 204 No Content
< Server: Apache-Coyote/1.1
< Date: Fri, 03 Apr 2015 03:12:59 GMT
<
* Connection #0 to host localhost left intact

deleteCustomer メソッドで @ResponseStatus を NO_CONTENT にしているので戻り値のボディはなし。

顧客全件取得

実際に削除できたのか、顧客全件取得する。

curl http://localhost:8080/api/customer -v -X GET

全件取得なのでURLの最後にIDは指定しない。
-X で HTTP メソッドの「GET」を送り、CustomerRestControllerクラス の引数なしの getCustomer メソッドを呼ぶ。

実行結果は下記の通り。

# curl http://localhost:8080/api/customer -v -X GET
*   Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET /api/customer HTTP/1.1
> User-Agent: curl/7.41.0
> Host: localhost:8080
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Fri, 03 Apr 2015 03:17:41 GMT
<
[]* Connection #0 to host localhost left intact

テーブルに一件も入っていなので空のボディが返ってくる。

わかりにくいので下記を実行して 2 件追加する。

curl http://localhost:8080/api/customer -v -X POST -H "Content-Type: application/json" -d "{\"name\":\"name02\", \"email\":\"name02@test\"}"
curl http://localhost:8080/api/customer -v -X POST -H "Content-Type: application/json" -d "{\"name\":\"name03\", \"email\":\"name03@test\"}"

もう一度、顧客全件取得する。

curl http://localhost:8080/api/customer -v -X GET

実行結果は下記の通り。

# curl http://localhost:8080/api/customer -v -X GET
*   Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET /api/customer HTTP/1.1
> User-Agent: curl/7.41.0
> Host: localhost:8080
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Fri, 03 Apr 2015 03:28:03 GMT
<
[{"id":2,"name":"name02","email":"name02@test"},{"id":3,"name":"name03","email":"name03@test"}]* Connection #0 to host localhost left intact

読みやすくすると以下の通り。

{
    "id":2,
    "name":"name02",
    "email":"name02@test"
},
{
    "id":3,
    "name":"name03",
    "email":"name03@test"
}
顧客一件取得

まだ実行していない処理、顧客一件取得を試す。

curl http://localhost:8080/api/customer/2 -v -X GET

URLの最後に取得対象のID「2」を指定する。これは先ほど追加したID。
-X で HTTP メソッドの「GET」を送り、CustomerRestControllerクラス の今度は引数ありの getCustomer メソッドを呼ぶ。
   URLの最後に付加したIDを、@PathVariable で引数 id にマッピングする。

実行結果は下記の通り。

# curl http://localhost:8080/api/customer/2 -v -X GET
*   Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET /api/customer/2 HTTP/1.1
> User-Agent: curl/7.41.0
> Host: localhost:8080
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Fri, 03 Apr 2015 03:36:28 GMT
<
{"id":2,"name":"name02","email":"name02@test"}* Connection #0 to host localhost left intact

読みやすくすると以下の通り。

{
    "id":2,
    "name":"name02",
    "email":"name02@test"
}

うん。
確かに 1 件だけ取得できた。

まとめ

RestController で REST のリクエストを取得し、
Service でビジネスロジックを書いて(今回は処理らしい処理は書いていないが)、
Repository で永続化をする。
ここまでを一気にまとめた。

次回は Controller と UI まわりをやってみよう。

■ 参考文献 ■

この記事で参考にしたのは「はじめての Spring Boot」です。

Groovyにプロキシ設定

groovyコマンドに下記の引数を追加してプロキシサーバーを指定できる。

groovy -DproxyHost=xxx.xxx.xxx.xxx -DproxyPort=xxx


ただし、コマンドを実行するたびに引数を指定するのは面倒なので、ホームディレクトリに下記フォルダとファイルを作成して、そこに記述すると良い。

Windowsの場合
.groovy/preinit.bat

下記のように記述する。

set JAVA_OPTS=-DproxyHost=xxx.xxx.xxx.xxx -DproxyPort=3128

バッチファイルとして記述する。

UNIX/Linux系の場合
.groovy/startup

下記のように記述する。

JAVA_OPTS="-DproxyHost=xxx.xxx.xxx.xxx -DproxyPort=3128"

シェルとして記述する。

根拠

この辺りのパラメータはstartGroovyを見ると分かる。
Windowsの場合はstartGroovy.batの下記の記述。

if exist "%USERPROFILE%/.groovy/preinit.bat" call "%USERPROFILE%/.groovy/preinit.bat"

UNIX/Linux系の場合はstartGroovyの下記の記述。

GROOVY_STARTUP="$HOME/.groovy/startup"
if [ -r "$GROOVY_STARTUP" ] ; then
    . "$GROOVY_STARTUP"
fi

sourceしているのでstartupには実行権限はいらない。
(たぶん。Windowsでしか試していない・・・)

Spring Boot/第三回 Spring Bootでデータベース操作(JPA編)

Spring Bootでデータベース操作をする。

Spring Bootでのデータベース操作は

の2種類があるが、今回はORマッピングの方の話。
JavaのORマッピングの仕様「JPAJava Persistence APIJavaの永続化のAPI)」でデータベースを操作する。

依存関係の追加

まずはMavenの依存関係の変更。

  <dependencies>
    <!-- append start -->
    <dependency>  <!-- a -->
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>  <!-- b -->
      <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
    </dependency>
    <dependency>  <!-- c -->
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.14.0</version>
      <scope>provided</scope>
    </dependency>
    <!-- append end -->
  </dependencies>

a -> Spring BootでJPAを扱うための依存関係。
b -> H2の依存関係。
c -> Lombokの依存関係。

JDBCを使うときは「spring-boot-starter-jdbc」だが、JPAを使うときは「spring-boot-starter-data-jpa」を指定する。


依存関係を見てみる

mvn dependency:tree

結果は以下の通り。

[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building SpringSample03 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ SpringSample03 ---
[INFO] tools.springsample.springsample03:SpringSample03:jar:1.0-SNAPSHOT
[INFO] +- junit:junit:jar:3.8.1:test
[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:1.1.5.RELEASE:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter:jar:1.1.5.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot:jar:1.1.5.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-autoconfigure:jar:1.1.5.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-starter-logging:jar:1.1.5.RELEASE:compile
[INFO] |  |  |  +- org.slf4j:jul-to-slf4j:jar:1.7.7:compile
[INFO] |  |  |  +- org.slf4j:log4j-over-slf4j:jar:1.7.7:compile
[INFO] |  |  |  \- ch.qos.logback:logback-classic:jar:1.1.2:compile
[INFO] |  |  |     \- ch.qos.logback:logback-core:jar:1.1.2:compile
[INFO] |  |  \- org.yaml:snakeyaml:jar:1.13:runtime
[INFO] |  +- org.springframework.boot:spring-boot-starter-tomcat:jar:1.1.5.RELEASE:compile
[INFO] |  |  +- org.apache.tomcat.embed:tomcat-embed-core:jar:7.0.54:compile
[INFO] |  |  +- org.apache.tomcat.embed:tomcat-embed-el:jar:7.0.54:compile
[INFO] |  |  \- org.apache.tomcat.embed:tomcat-embed-logging-juli:jar:7.0.54:compile
[INFO] |  +- com.fasterxml.jackson.core:jackson-databind:jar:2.3.3:compile
[INFO] |  |  +- com.fasterxml.jackson.core:jackson-annotations:jar:2.3.3:compile
[INFO] |  |  \- com.fasterxml.jackson.core:jackson-core:jar:2.3.3:compile
[INFO] |  +- org.hibernate:hibernate-validator:jar:5.0.3.Final:compile
[INFO] |  |  +- javax.validation:validation-api:jar:1.1.0.Final:compile
[INFO] |  |  +- org.jboss.logging:jboss-logging:jar:3.1.1.GA:compile
[INFO] |  |  \- com.fasterxml:classmate:jar:1.0.0:compile
[INFO] |  +- org.springframework:spring-core:jar:4.0.6.RELEASE:compile
[INFO] |  +- org.springframework:spring-web:jar:4.0.6.RELEASE:compile
[INFO] |  |  +- org.springframework:spring-aop:jar:4.0.6.RELEASE:compile
[INFO] |  |  |  \- aopalliance:aopalliance:jar:1.0:compile
[INFO] |  |  +- org.springframework:spring-beans:jar:4.0.6.RELEASE:compile
[INFO] |  |  \- org.springframework:spring-context:jar:4.0.6.RELEASE:compile
[INFO] |  \- org.springframework:spring-webmvc:jar:4.0.6.RELEASE:compile
[INFO] |     \- org.springframework:spring-expression:jar:4.0.6.RELEASE:compile
[INFO] +- org.springframework.boot:spring-boot-starter-test:jar:1.1.5.RELEASE:test
[INFO] |  +- org.mockito:mockito-core:jar:1.9.5:test
[INFO] |  |  \- org.objenesis:objenesis:jar:1.0:test
[INFO] |  +- org.hamcrest:hamcrest-core:jar:1.3:test
[INFO] |  +- org.hamcrest:hamcrest-library:jar:1.3:test
[INFO] |  \- org.springframework:spring-test:jar:4.0.6.RELEASE:test
[INFO] +- org.springframework.boot:spring-boot-starter-data-jpa:jar:1.1.5.RELEASE:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-aop:jar:1.1.5.RELEASE:compile
[INFO] |  |  +- org.aspectj:aspectjrt:jar:1.8.1:compile
[INFO] |  |  \- org.aspectj:aspectjweaver:jar:1.8.1:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-jdbc:jar:1.1.5.RELEASE:compile
[INFO] |  |  +- org.springframework:spring-jdbc:jar:4.0.6.RELEASE:compile
[INFO] |  |  +- org.apache.tomcat:tomcat-jdbc:jar:7.0.54:compile
[INFO] |  |  |  \- org.apache.tomcat:tomcat-juli:jar:7.0.54:compile
[INFO] |  |  \- org.springframework:spring-tx:jar:4.0.6.RELEASE:compile
[INFO] |  +- org.hibernate:hibernate-entitymanager:jar:4.3.5.Final:compile
[INFO] |  |  +- org.jboss.logging:jboss-logging-annotations:jar:1.2.0.Beta1:compile
[INFO] |  |  +- org.hibernate:hibernate-core:jar:4.3.5.Final:compile
[INFO] |  |  |  +- antlr:antlr:jar:2.7.7:compile
[INFO] |  |  |  \- org.jboss:jandex:jar:1.1.0.Final:compile
[INFO] |  |  +- dom4j:dom4j:jar:1.6.1:compile
[INFO] |  |  |  \- xml-apis:xml-apis:jar:1.0.b2:compile
[INFO] |  |  +- org.hibernate.common:hibernate-commons-annotations:jar:4.0.4.Final:compile
[INFO] |  |  +- org.hibernate.javax.persistence:hibernate-jpa-2.1-api:jar:1.0.0.Final:compile
[INFO] |  |  +- org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec:jar:1.0.0.Final:compile
[INFO] |  |  \- org.javassist:javassist:jar:3.18.1-GA:compile
[INFO] |  +- org.springframework:spring-orm:jar:4.0.6.RELEASE:compile
[INFO] |  +- org.springframework.data:spring-data-jpa:jar:1.6.2.RELEASE:compile
[INFO] |  |  +- org.springframework.data:spring-data-commons:jar:1.8.2.RELEASE:compile
[INFO] |  |  +- org.slf4j:slf4j-api:jar:1.7.7:compile
[INFO] |  |  \- org.slf4j:jcl-over-slf4j:jar:1.7.7:compile
[INFO] |  \- org.springframework:spring-aspects:jar:4.0.6.RELEASE:compile
[INFO] +- com.h2database:h2:jar:1.3.176:compile
[INFO] \- org.projectlombok:lombok:jar:1.14.0:provided
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.135 s
[INFO] Finished at: 2015-03-23T00:53:09+09:00
[INFO] Final Memory: 12M/29M
[INFO] ------------------------------------------------------------------------

org.springframework.boot:spring-boot-starter-data-jpaにぶら下がっているのがorg.hibernate:hibernate-entitymanagerであることから、JPAの実装としてHibernateであることがわかる。
JPAは仕様であって、それを実装したライブラリを実際には使用する。
実装したライブラリのことを「JPAプロバイダ」と言う。
なので、「今回のJPAプロバイダはHibernate」となる。

Hibernateの設定

Hibernateの設定をapplication.ymlに追加する。

src/main/resources/application.yml

このファイルに下記の内容を記載する。

spring:
  datasource:
    driverClassName: org.h2.Driver
    url: jdbc:h2:file:testdb/testdb
    username: sa
    password:
  jpa:
    hibernate:
      ddl-auto: update

datasourceの設定は準備編と同じ。
jpaからの設定を追加する。

ddl-autoはHibernateによるテーブル作成をどう制御するかを定義する。
以下のものがある。

  • create-drop -> テーブルがあったら削除してから作り直す。なかったら作る。つまりデータは毎回消える。(UTに便利?)
  • update -> テーブルの作り直しはしないが、なかったら作る。
  • none -> なくても作らない。本番運用向き。

何を基にテーブルを作るかというと、下記で説明するエンティティクラスを基に作る。
なので、準備編で作成したschema.sqlは不要なので削除する。

エンティティクラスを用意する

準備編で作成したCustomerクラスをJPA用にカスタマイズする。

package tools.springsample.springsample03.domain;

import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.Id;
import javax.persistence.GeneratedValue;
import javax.persistence.Column;
import lombok.Data;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

@Entity    // a
@Table(name="customer")    // b
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Customer {
    @Id    // c
    @GeneratedValue    // d
    private Integer id;

    @Column(nullable=false)    // e
    private String name;

    @Column(nullable=false)    // e
    private String email;
}

JPA用に変更したのは下記の通り。
a -> エンティティクラスであることを記すアノテーション
b -> どのテーブルとマッピングするのか指定する。
c -> フィールドidがエンティティの主キーであることを表す。
d -> 自動採番する主キーであることを表す。
e -> テーブルに該当するカラムであることを表す。nullでの更新を抑制する。

リポジトリクラスを用意する

今まで作成したCustomerRepositoryクラスをJPA用に変更する。
(変更というかほとんど書き換え)

package tools.springsample.springsample03.repository;

import tools.springsample.springsample03.domain.Customer;

import org.springframework.data.jpa.repository.JpaRepository;

public interface CustomerRepository
    extends JpaRepository<Customer, Integer> {
}

クラスではなくインターフェースになる。
JpaRepositoryを拡張すると、実行時に実装クラスが自動生成される。
CRUDの各メソッドが用意されているので、それを呼ぶとデータベースを操作できる。
JPAを使ってSQLがソースから消えた。

コンパイルして実行

JDBC編で作成したAppクラスはそのまま使えるので、ここまでの状態でコンパイルして実行することができる。

mvn package

続いて起動。

java -jar target\SpringSample03-1.0-SNAPSHOT.jar

実行したあとデータベースを見て登録されることを確認すること。

やったことと言えば、1:設定書いて、2:エンティティ作って、3:リポジトリの宣言、の3ステップだけ。
これだけでデータベース更新できてしまうのが、手軽すぎて逆に怖い。

ただ、不明なことも見えてきた。

  • JpaRepositoryを拡張して出来る検索は全検索かPKでの検索だけだが、他のキーで検索するにはどうする?
  • ほかのテーブルをJOINして検索する場合はどうする?
  • JpaRepositoryに主キーを知らせる(今回はInteger)が、複合主キー(複数カラムを用いた主キー)の場合はどうする?

これらは追々。

■ 参考文献 ■

この記事で参考にしたのは「はじめての Spring Boot」です。