Spring Bootを紹介してみる

はじめに

この記事は SLP KBIT Advent Calendar 2019 の3日目の記事です。

adventar.org

最後の一枠余っていたので急遽書きました。
今回は、最近自分が一番使っていてるであろうSpring Bootを紹介したいと思います。

Spring Bootってなんぞや?

Spring BootとはJavaやKotlinなどで利用できるWebアプリケーションフレームワークです。 Spring Bootの特徴は調べてもらったらいくらでもでてくるので割愛します。 Spring Bootの良さはたくさんありますが、個人的に良いなと思っているのは以下の点です。

  • データベースとの連携を行うツールやクラウドサービスと連携するためのSDKなど、ライブラリが豊富
  • アーキテクチャ設計の自由度が高い
  • jarファイルに固めることができるのでデプロイが簡単

特にライブラリが豊富な点は個人的に良いなと感じており、プロジェクトの規模感に応じてORマッパーを使い分けたりできるのは魅力的です。 また、アーキテクチャ設計の自由度が高い点もプロジェクトの規模感に応じて使い分けれるのでとても良いところだなと感じています。

実際に使ってみる

今回は、Spring Bootでメモ管理システムを作ります。
基本のCRUD機能を実装して完成となります。

使用技術

  • Java11
  • Spring Boot 2.2.1
  • Gradle
  • H2 DataBase
  • JPA
  • Flaway
  • Lombok
  • Thymeleaf

IDEEclipse_2019-09を利用しました。

プロジェクトの立ち上げ

Spring Bootのプロジェクトを立ち上げにはSpring Initializerを使います。

https://start.spring.io

今回は使用技術にあるように、Java11、Spring Boot2.2.1、Gradleを利用していきます。
それぞれ必要なライブラリ群を選択しGenerateするとそれらのライブラリが入った状態のプロジェクトがダウンロードできます。
この時点でライブラリを入れ忘れても、あとでbuild.gradleに書き込めば追加できます。
その後Eclipseを起動し、ファイル -> インポート から 既存のGradleプロジェクトから先程のプロジェクトをインポートします。

実装するエンドポイント

今回実装するエンドポイント一覧です。 Webページは1枚で、メモ一覧表示用にすべてのフォームを実装します。 POST、PUT、DELETEのリクエストが来たときには処理を行った後そのページにリダイレクトするようにしています。

エンドポイント method 説明
/memos GET メモ一覧表示用
/memos POST メモ作成用
/memos/:id PUT メモ更新用
/memos/:id DELETE メモ削除用

テーブル構成

今回はmemoテーブルのみを作成します。カラムの制約はあえてなしにしています。

memo

カラム名
id BIGINT
body VARCHAR(256)
created_at TIMESTAMP
updated_at TIMESTAMP

アーキテクチャ

Spring Bootでは3層アーキテクチャが使われることが多いです。そこで今回は、そのアーキテクチャを少し改造してcontroller層、service層、repository層、entity層に分けて実装します。 3層アーキテクチャは以下の記事が参考になると思います。

qiita.com

controller層

controllerではリクエストが来た際にどの様な処理を行うのかを実装します。
見慣れないアノテーションがたくさんあったりと複雑そうに見えますが、基本的には来たリクエストをservice層で処理し、その後必要な結果を返すといった実装となっています。 @AutowiredはDependency Injection、いわゆるDIを自動で行ってくれるものです。(DIの説明に関しては今回は省きます) とりあえず今はmemoService変数には、MemoServiceのインスタンスが入っていると思ってくれれば良いかなと思います。

package work.tomosse.AdventCalendarProject.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import work.tomosse.AdventCalendarProject.entity.Memo;
import work.tomosse.AdventCalendarProject.service.MemoService;

@Controller
@RequestMapping("/memos")
public class MemoController {

    @Autowired
    MemoService memoService;

    /**
     * メモ一覧表示
     *
     * @param mav
     * @return
     */
    @GetMapping
    public ModelAndView index(final ModelAndView mav) {
        mav.addObject("memoList", memoService.getMemo());
        mav.setViewName("index");
        return mav;
    }

    /**
     * メモ作成
     *
     * @param memo
     * @return
     */
    @PostMapping
    public String create(@ModelAttribute final Memo memo) {
        memoService.createMemo(memo);
        return "redirect:/memos";
    }

    /**
     * メモ更新
     *
     * @param memo
     * @return
     * @throws Exception
     */
    @PutMapping("/{id}")
    public String update(@ModelAttribute final Memo memo) throws Exception {
        memoService.updateMemo(memo);
        return "redirect:/memos";
    }

    /**
     * メモ削除
     *
     * @param id
     * @return
     */
    @DeleteMapping("/{id}")
    public String delete(@PathVariable final Long id) {
        memoService.deleteMemo(id);
        return "redirect:/memos";
    }
}

service層

serviceでは、controllerから送られてきたデータをもとに処理を行います。
今回の場合だと、データベースを使った処理を行うことがほとんどになりましたが、複雑なシステムになるとここでの処理が増えることになります。

package work.tomosse.AdventCalendarProject.service;

import java.util.Date;
import java.util.List;

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

import work.tomosse.AdventCalendarProject.entity.Memo;
import work.tomosse.AdventCalendarProject.repository.MemoRepository;

@Service
public class MemoService {

    @Autowired
    MemoRepository memoRepository;

    /**
     * memoの一覧を返却する
     *
     * @return
     */
    public List<Memo> getMemo() {
        return memoRepository.findAll();
    }

    /**
     * memoを作成する
     *
     * @param body
     */
    public void createMemo(final Memo memo) {
        memo.setCreatedAt(new Date());
        memo.setUpdatedAt(new Date());
        memoRepository.save(memo);
    }

    /**
     * メモを更新する
     *
     * @param body
     * @throws Exception
     */
    public void updateMemo(final Memo memo) throws Exception {
        memo.setUpdatedAt(new Date());
        memoRepository.save(memo);
    }

    /**
     * memoを削除する
     *
     * @param id
     */
    public void deleteMemo(final Long id) {
        memoRepository.deleteById(id);
    }
}

repository層

repository層はDBへのCRUDを自動で実装してくれるインタフェースが集まった層となります。 JpaRepositoryを継承しただけなので説明は省きます。

entity層

entityは今回の場合だとmemoテーブルの構造を表しています。
@Columnでデータベースのカラムとの紐付けを行っています。
本来であれば、Getter/Setterを定義しますが、それをベタに書いてしまうとすごく長いクラスとなるので、Lombokを使って省略します。(@Dataの部分)
LombokJavaを書く上ですごく便利なライブラリなので積極的に使うと良いと思います。

package work.tomosse.AdventCalendarProject.entity;

import java.util.Date;

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

import lombok.Data;

@Entity
@Data
public class Memo {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column
    private Long id;

    @Column(name="body")
    private String body;

    @Column(name="created_at")
    private Date createdAt;

    @Column(name="updated_at")
    private Date updatedAt;
}

表示部分

表示部分はThymeleafで実装します。といっても普通のHTMLベースでの書き方なので詰まることは少ないと思います。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>AdventCalendarProject</title>
</head>
<body>
  <h1>AdventCalandarProject</h1>
  <label>登録</label>
  <form th:method="POST" th:action="@{/memos}">
    <input type="text" name="body" />
    <input type="submit" value="登録" />
  </form>
  <table border="1">
    <tr>
      <th>ID</th>
      <th>内容</th>
      <th>作成日</th>
      <th>更新日</th>
      <th>更新</th>
      <th>削除</th>
    </tr>
    <tr th:each="memo: ${memoList}">
      <td th:text="${memo.getId()}"></td>
      <td th:text="${memo.getBody()}"></td>
      <td th:text="${memo.getCreatedAt()}"></td>
      <td th:text="${memo.getUpdatedAt()}"></td>
      <td>
        <form th:method="PUT" th:action="@{'/memos/' + ${memo.getId()}}">
          <input type="text" name="body" th:value="${memo.getBody()}" />
          <input type="submit" value="更新" />
        </form>
      </td>
      <td>
        <form th:method="DELETE" th:action="@{'/memos/' + ${memo.getId()}}">
          <input type="submit" value="削除" />
        </form>
      </td>
    </tr>
  </table>
</body>
</html>

実際の画面

実際の画面がこちらです。 ちゃんとメモの登録、作成、削除ができるようになっています。

f:id:hacktomo:20191202051325p:plain
画面

作ったもの

github.com

まとめ

かなりざっくりですが、Spring Bootの紹介をしました。
Spring Bootは使えば使うほど良さがわかるフレームワークだと思います。
最初は慣れるまで大変だと思いますが、根気よく使ってみてください。
きっと良さがわかると思います。

ps. Java + Lombokめっちゃ便利なのでぜひ使ってほしい...!!