見出し画像

【SpringBoot+Docker】Web MVC ベース その③~構造を知ることで Spring Boot を使いこなせる(後編)~

前回は Spring Boot で開発するアプリケーションの基本構造を図で説明しました。
今回はソースコードで説明します。
Spring Boot は構造に沿って作ってしまえば拍子抜けするほど簡単にアプリケーションが動作します。
仕組みを理解しようとするとかえって難しいので、まずはあまり深く考えずに構造だけを覚えていきましょう。

前回の記事を読んでいない方はまずはそちらを読んで下さい。
ソースコードもそちらの記事にあります。

この記事は Java、Docker、Visual Studio Code(以下、vscode)、シェルなどのプログラミングに関する基本的な技術を知っている、または、調べれば分かる程度の知識がある前提で書いています。
分かりづらいところがあったらコメントでお知らせ下さい。

今日のノウハウ

Spring Boot の基本構造を知る

今回は前回に引き続き基本構造を知ることです。

主なディレクトリ

サンプルの各コンポーネントは次のディレクトリに配置されています。
まずは基本コンポーネントだけを説明します。
その他のディレクトリについては個別に説明します。

./src/main/java/changeit/demo/entity       # エンティティ
./src/main/java/changeit/demo/repository   # リポジトリ、JPA リポジトリ
./src/main/java/changeit/demo/users        # コントローラー、サービス実装・インタフェース、DTO など
./src/main/resources/templates/users       # テンプレート

エンティティとリポジトリはそれぞれ専用のディレクトリに配置します。
コントローラー、サービス実装・インタフェース、DTO はセットで作成するのでディレクトリをまとめます。
users ディレクトリにしてあるのは users テーブルを管理する機能だからです。
機能単位に分けると覚えておいて下さい。
テンプレートも同様です。

次に各コンポーネントについて説明をします。
動作までは説明をしません。
簡単にこんなことが書かれているということを説明します。

コントローラー

コントローラの役目はブラウザからリクエストを受け、サービスに情報を渡したり、サービスから情報を取得したりして、ブラウザにレスポンスを返すことです。
レスポンスを返す方法にはテンプレートを使う方法やリダイレクトを返すなどの方法があります。
クラスに @Controller を付けるとコントローラーになります。

@Controller                                                         // コントローラーとして扱う
@RequiredArgsConstructor                                            // 必須フィールドのコンストラクターを生成
public class UsersController {

  private final UsersService usersService;                          // users サービスを注入

  @GetMapping("/users")                                             // /users にマッピング
  public String indexPage(Model model) {                            // model はビューへオブジェクト受け渡す役
    List<User> userList = usersService.findAll();                   // user リストを取得する。
    model.addAttribute(userList);                                   // user リストをビューに渡す。userList でアクセスできる。コレクションは先頭小文字のクラス名+List
    return "users/index";                                           // template を指定する
  }

  ※省略

  @PostMapping("/users/create")                                     // /users/new にマッピング
  public String create(UserForm userForm) {                         // フォームデータを userForm に受け取る
    usersService.create(userForm);                                  // user を DB に新規登録
    return "redirect:/users";                                       // リダイレクト先を指定する
  }

  ※省略

サンプルでは次の5つのことをしています。

GET、POST などのブラウザからのリクエストを受ける。

メソッドに @GetMapping、@PostMapping を付けるとブラウザのリクエストを受けるメソッドになります。
メソッドで各リスエストを処理するコードを書きます。

サービスを呼び出す

クラスに @RequiredArgsConstructor を付けて、private final フィールドでサービスを定義すると、サービスを引数に含むコンストラクタが生成されます。
そうすると、フィールドにサービスのインスタンスが注入され、サービスを呼び出すことができるようになります。
コントローラーはフィールドを通じてサービスを呼び出します。

テンプレートを指定する。

メソッドが "users/index" を返すとテンプレートの users/index.html を指定したことになります。

テンプレートに情報を渡す。

メソッドの引数に Model model を含めて、model.addAttribute メソッドで渡したい情報を指定します。
そうするとテンプレートに情報を渡すことができます。
前項のテンプレートを指定するときに使います。

リダイレクトする。

メソッドが "redirect: " で始まる文字列を返すと "redirect: " 以降に書かれたパスにリダイレクトします。

サービスインタフェース・サービス実装

サービスインタフェースとサービス実装はセットで書いて下さい。
細かく書きませんが、トランザクションが関係しています。
合わせてサービスと表現します。

サービスの役目はビジネスロジックを実行することです。
実際のビジネスロジックはエンティティや DTO に書いたり、値オブジェクトに書いたり、ビジネスロジッククラスに書いたりします。

public interface UsersService {             // users サービスインタフェース

  List<User> findAll();

  ※省略
@Service                                                  // サービスとして扱う
@RequiredArgsConstructor                                  // 必須フィールドのコンストラクターを生成
public class UsersServiceImpl implements UsersService {

  private final UserRepository userRepository;            // users リポジトリを注入

  @Override
  public UserForm buildForm(Long id) {
    User user = userRepository.find(id);                  // user を取得する
    return new UserForm(user);
  }

  ※省略

サービスインタフェース

コントローラーや他のサービスに公開するメソッドを書きます。
必ず作成して下さい。

サービス実装

クラスに @Service を付けて、サービスインタフェースを実装するとサービスになります。
サンプルでは主にリポジトリを呼び出しています。

コントローラと同じで private final と @RequiredArgsConstructor を使う仕組みでリポジトリを注入することができます。
サービスはフィールドを通じてリポジトリを呼び出します。

エンティティ

エンティティはデータベースのテーブルとマッピングされ、リポジトリからテンプレートまで情報を受け渡すこととエンティティ固有のビジネスロジックを実行するのが役目です。

@Entity                                                 // JPA エンティティとして扱う
@Table(name = "users")                                  // users テーブルとマッピングする
@Data                                                   // getXxx()、setXxx() を生成
@NoArgsConstructor                                      // 引数なしコンストラクターを生成
public class User {

  @Id                                                   // ID 列として扱う
  @GeneratedValue(strategy = GenerationType.IDENTITY)   // データベースが ID を生成
  @Column                                               // id カラムにマッピング
  private long id;

  @Column                                               // username カラムにマッピング
  private String username;

  @Column                                               // password カラムにマッピング
  private String password;

  @Column                                               // enabled カラムにマッピング
  private boolean enabled;

}

クラスに @Entity、@Table を付けるとエンティティになります。
カラムに対応するフィールドに @Column、id 列に対応するフィールドに @Id を付けることでマッピングができます。
あとは適宜定義します。
細かくはいずれ説明します。

DTO

DTO はエンティティと同じでリポジトリからテンプレートまで情報を受け渡すことと DTO 固有のビジネスロジックを実行するのが役目ですが、データベースのテーブルとは関連付けられません。

@Data                                     // getXxx()、setXxx() を生成
@NoArgsConstructor                        // 引数なしコンストラクターを生成
public class UserForm {

  private Long id;                        // ビュー側の id に対応

  private String username;                // ビュー側の username に対応

特に作成ルールはなくて普通のクラスです。
サンプルには入力フォームに対応する DTO を定義してあります。

リポジトリ

リポジトリの役目はエンティティを使って、データベースのテーブルやビューから情報を読み込んだり、テーブルに情報を書き込んだりする役目です。

@Repository                                       // レポジトリとして扱う
@RequiredArgsConstructor                          // 必須フィールドのコンストラクターを生成
public class UserRepository {                     // 公開 User リポジトリ

  private final UserJpaRepository jpaRepository;  // JPA 委譲先

  public User find(Long id) {                     // user を取得する
    return jpaRepository.getReferenceById(id);
  }

  ※省略
 
interface UserJpaRepository extends JpaRepository<User, Long> { // JPA リポジトリ
  List<User> findByOrderById();                                 // クエリメソッド

  ※省略

クラスに @Repository を付けるとリポジトリになります。
private final と @RequiredArgsConstructor を使う仕組みで JPA リポジトリを注入して委譲先として使います。
こうしておくと動的に JPQL 組み立てたり、JPA リポジトリへのアクセスを制限したり、クエリメソッドの名前付けルールに縛られない公開メソッドが作れたりといろいろなメリットがあります。

テンプレート

テンプレートの役目はブラウザに返す HTML を生成することです。
Thymeleaf を使用します。

    <tbody>
      <tr th:each="user : ${userList}" th:object="${user}">                 <!-- userList を繰り返し処理する。tr 内で user を参照 -->
        <td><a th:href="@{/users/{id}(id=*{id})}" th:text="*{id}"></a></td> <!-- /users/ID にリンク。user.id を表示 -->
        <td th:text="*{username}"></td>                                     <!-- user.username を表示 -->
        <td th:text="*{password}"></td>                                     <!-- user.password を表示 -->
        <td th:text="*{enabled}"></td>                                      <!-- user.enabled を表示 -->
        <td>
          <a th:href="@{/users/{id}/edit(id=*{id})}">edit</a> |             <!-- /users/ID/edit にリンク -->
          <form th:action="@{/users/{id}/delete(id=*{id})}"
              method="post" style="display: inline;">                       <!-- /users/ID/delete に post -->
            <button>delete</button>
          </form>
        </td>
      </tr>
    </tbody>

細かい機能の説明は別でしますが、userList がコントローラーから受け渡された情報です。
テンプレートはコントローラーから渡された情報を使って HTML を生成します。

まとめ

簡単には理解できないと思いますが、まずは構造を知って下さい。
単純な管理画面は Spring Boot を使うとこのくらいのコードで作れてしまいます。
ただ、この基本構造では貧弱過ぎて業務システムは作れません。

これからこの構造に応用コンポーネントを付け加えていきます。


この記事が気に入ったらサポートをしてみませんか?