【Spring Framework】新卒未経験によるSpring MVCでのログインシステム作成

はじめまして!今年新卒として入社したエンジニアリングソリューション事業部のMです!
今回は、3か月間の新卒研修の中で使用していた「Spring Framework」について、
復習とアウトプットも兼ねて共有させていただきます!

目次

  1. Springフレームワークとは
  2. ログインシステムの作成
    1. インストール
    2. データベースの作成
    3. Springスタータープロジェクトの作成
    4. 各種設定
    5. プログラムの記述
  3. まとめ

Springフレームワークとは


Springフレームワークとは、Javaプラットフォームにおいて使用できる
オープンソースのアプリケーションフレームワークです。
DI(Dependency Injection)やAOP(Aspect Oriented Programming)をサポートしているので、
効率的なアプリケーション開発を行うことができます。

 ※DI:依存性の注入。オブジェクトの成立要件に必要な情報を外部で設定する。
 ※AOP:アスペクト指向プログラミング。クラスには本質的な処理のみ記述し、本質的でない共通化可能な処理はアスペクトに記述する。

実際に使用した感想としては、
使用以前と比較して各クラスの役割がわかりやすくなり、
DIがサポートされていることで後々の修正がしやすい等の魅力を感じました。

学習期間は非常に短かったですが、基礎的なプログラムはすぐに作れるようになりました。
皆さんも簡単に使用できると思います!

本記事では、SpringフレームワークのSpring MVCを用いて
実際に「ログインシステム」を作成する工程を共有します。

皆様にSpringフレームワークの魅力を少しでも伝えられたら本望です。

ログインシステムの作成


今回作成するログインシステムはユーザーIDとパスワード認証でログインするという非常にシンプルなシステムです。
データベースに保存したユーザー情報と入力値を照合して、ログインチェックを行います。
本記事で作成するログインシステムの概要
図:本記事で作成するログインシステムの概要

① インストール

まずSpringフレームワークを使用可能なJavaプラットフォームをインストールします。
本記事ではSTS(Spring Tool Suite)と呼ばれるソフトを使用します。 ※EclipseにSpringをダウンロードして使用することも可能です。

(STS)https://spring.io/tools
(Eclipse)https://www.eclipse.org/downloads/

② データベースの作成

本記事ではMySQLを使用してデータベースの構築、操作を行います。
DB名:login テーブル名:user
作成するテーブルの情報

③ Springスタータープロジェクトの作成

まず「ファイル > 新規 > Springスターター・プロジェクト」から新規プロジェクトの作成を行います。
 Springスターター・プロジェクトの場所
プロジェクト名、グループ、パッケージをそれぞれ入力して「次へ」を選択します。
 Springスターター・プロジェクトの設定項目
使用する機能(ライブラリ)を選択します。今回は「JDBC API」「MySQL Driver」「Spring Web」を使用します。
依存関係の設定

④ 各種設定

< pom.xml >
今回利用する各機能を設定します。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>login</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>login</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <!-- 追加 -->
        <maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
    </properties>

    <dependencies>
        <!-- ///ここから追加/// -->
        
        <!-- Bean Validation導入 -->
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
        </dependency>
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.glassfish</groupId>
            <artifactId>javax.el</artifactId>
            <version>3.0.1-b09</version>
        </dependency>

        <!-- jsp用 -->
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <scope>provided</scope>
        </dependency>

        <!-- JSTLを使用 -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
        </dependency>
        
        <!-- 自動リロード -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
        
        <!-- ///ここまで追加/// -->

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

28~41行目で導入している「Bean Vallidation」とは、アノテーションの記載のみで入力値をチェックすることができるSpringフレームワークのライブラリです。

< application.properties >
データベース(DB)に接続するための設定をします。
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/login?serverTimezone=JST
spring.datasource.username=root
spring.datasource.password=mFieldUser

spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
spring.datasource.urlはjdbc:mysql://{ホスト名}:{ポート番号}/{DB名}?serverTimezone=JST
< ValidationMessage_ja.properties(src/main/resourcesに追加)>
Springの入力チェック機能であるBean Vallidationに関する設定をします。
javax.validation.constraints.NotNull.message={0}\u306f\u5fc5\u9808\u3067\u3059\u3002
javax.validation.constraints.NotBlank.message={0}\u306f\u5fc5\u9808\u3067\u3059\u3002

⑤ プログラムの記述

Spring MVCは処理(Model)、画面(View)、制御(Controller)の3種類のプログラムから構成されます。
今回作成するログインシステムのSpring MVCでの処理の流れは以下のようになります。
Spring MVCでの処理の流れ
図 : Spring MVCでの処理の流れ
< jsp >
jspファイルはJavaを使用できるHTMLファイルのようなもので、画面(View)を担当しています。 Modelクラスに保存してある値を取得できる${}という書き方や、画面からの入力値をformクラスに保管することができるform:formタグ、xssやSQLインジェクションの対策用コード等、Spring MVCのjspファイルで使用可能な様々な機能が用意されています。
login.jsp(src/main/webapp/WEB-INF/views/login.jsp)
上記括弧内のディレクトリにlogin.jspファイルを新規作成します。
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ログイン</title>
<link href="css/main.css" rel="stylesheet" type="text/css">
<link href="css/login.css" rel="stylesheet" type="text/css">
<link
    href="https://fonts.googleapis.com/css2?family=M+PLUS+Rounded+1c:wght@300;400;500;700&display=swap"
    rel="stylesheet">
<link
    href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
    rel="stylesheet">
<script src="js/jquery-3.5.1.min.js"></script>
</head>
<body>
    <div class="login">
        <div class="white_background">
            <h2>SpringBoot<br><span>デモページ</span></h2>
        
            <c:if test="${not empty errMsg}">
                <h3 class="t_red">${errMsg}</h3>
            </c:if>

            <form:form action="home" modelAttribute="sampleModel" method="post">
                <label><h3>ユーザーID</h3></label>
                <form:input path="userId" placeholder="ユーザーID" />
                <label><h3>パスワード</h3></label>
                <form:password path="password" id="password" placeholder="パスワード" />
                <p class="login_password_check">
                    <input type="checkbox" id="show_pass">
                    <label for="show_pass">パスワードを表示</label>
                </p>
                <form:button type="submit">ログイン</form:button>
            </form:form>
        </div>
    </div>

    <!--JavaScript読み込み-->
    <script type="text/javascript" src="js/login.js"></script>
    
</body>
</html>
home.jsp(src/main/webapp/WEB-INF/views/home.jsp)
上記括弧内のディレクトリにhome.jspファイルを新規作成します。
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ログイン完了</title>
<link href="css/main.css" rel="stylesheet" type="text/css">
<link href="css/login.css" rel="stylesheet" type="text/css">
<link
    href="https://fonts.googleapis.com/css2?family=M+PLUS+Rounded+1c:wght@300;400;500;700&display=swap"
    rel="stylesheet">
<link
    href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
    rel="stylesheet">
<script src="js/jquery-3.5.1.min.js"></script>
</head>
<body>
    <div class="login">
        <div class="white_background">
            <h3 class="login_comp">ログイン完了!</h3>
            
            <p class="welcome">ようこそ!${loginUser.getUserName() }さん</p>
            
            <button onclick="location.href='login'" id="logout">ログアウト</button>
        </div>
    </div>

    <!--JavaScript読み込み-->
    <script type="text/javascript" src="js/login.js"></script>
</body>
</html>
< Entity >
Entityクラス(DTO)にはプログラムで使用するフィールドとアクセサーを用意します。
User(src/main/java/com/example/entity/User.java)
上記括弧内のディレクトリにUser.javaクラスを新規作成します。
package com.example.entity;

public class User {
  //ユーザー名
  private String userId;
  
  //パスワード
  private String password;
  
  //ユーザー名
  private String userName;

  //アクセサー
  public String getUserId() {
    return userId;
  }

  public void setUserId(String userId) {
    this.userId = userId;
  }

  public String getPassword() {
    return password;
  }

  public void setPassword(String password) {
    this.password = password;
  }

  public String getUserName() {
    return userName;
  }

  public void setUserName(String userName) {
    this.userName = userName;
  }
}
< Dao >
DaoクラスではSQL文とそれを実行するメソッドを用意します。
UserDao(src/main/java/com/example/dao/UserDao.java)
結合を疎にするためにインターフェースを介します。
上記括弧内のディレクトリにUserDao.javaクラスを新規作成します。
package com.example.dao;

import com.example.entity.User;

public interface UserDao {
  //ログインチェック用抽象メソッド
  public User findByIdAndPass(String userId, String password);
}
PgUserDao(src/main/java/com/example/dao/impl/PgUserDao.java)
上記括弧内のディレクトリにPgUserDao.javaクラスを新規作成します。
package com.example.dao.impl;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository;

import com.example.dao.UserDao;
import com.example.entity.User;

@Repository
public class PgUserDao implements UserDao{
  //SQL文
  private static final String SQL_SELECT_BY_ID_AND_PASS =
    "SELECT user_id, password, user_name FROM user WHERE user_id = :userId AND password = :password;";

  //プレースホルダー
  @Autowired
  private NamedParameterJdbcTemplate jdbcTemplate;

  //ログインチェック用抽象メソッド
  @Override
  public User findByIdAndPass(String userId, String password) {
    String sql = SQL_SELECT_BY_ID_AND_PASS;    
    //プレースホルダーに値を格納する
    MapSqlParameterSource param = new MapSqlParameterSource();
    param.addValue("userId", userId);
    param.addValue("password", password);
    
    List<User> userList = jdbcTemplate.query(sql, param,
        new BeanPropertyRowMapper<User>(User.class));
    
    //userListが空の場合はNull、空でない場合はUserを返す
    return userList.isEmpty() ? null : userList.get(0);
  }
}
< Service >
DAOを呼び出して、ビジネスロジックを実行するメソッドを用意します。
UserService(src/main/java/com/example/service/UserService.java)
上記括弧内のディレクトリにUserService.javaクラスを新規作成します。
package com.example.service;

import com.example.entity.User;

public interface UserService {
  public User findByIdAndPass(String userId, String password);
}
PgUserService(src/main/java/com/example/service/impl/PgUserService.java)
上記括弧内のディレクトリにPgUserService.javaクラスを新規作成します。
package com.example.service.impl;

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

import com.example.dao.UserDao;
import com.example.entity.User;
import com.example.service.UserService;

@Service
public class PgUserService implements UserService {

  @Autowired
  UserDao userDao;
  
  //ログインチェック用抽象メソッド
  @Override
  public User findByIdAndPass(String userId, String password) {
    return userDao.findByIdAndPass(userId, password);
  }
}
< Controller >
画面遷移に関する記述をします。
LoginController(src/main/java/com/example/controller/ LoginController.java)
上記括弧内のディレクトリにLoginController.javaクラスを新規作成します。
package com.example.controller;

import javax.servlet.http.HttpSession;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.example.controller.form.LoginForm;
import com.example.entity.User;
import com.example.service.UserService;

@Transactional
@Controller
public class LoginController {
  /**
   * userテーブル用サービス
   */
  @Autowired
  private UserService userService;
  
  /**
   * セッションに接続
   */
  @Autowired
  HttpSession session;
  
  /**
   * ログイン画面の表示
   */
  @RequestMapping("/login")
  public String login(@ModelAttribute("sampleModel") LoginForm loginForm,
      BindingResult bindingResult, Model model) {
    return "login";
  }
  
  /**
   * ホーム画面遷移
   */
  @RequestMapping(value = "/home", method = RequestMethod.POST)
  public String home(@Validated @ModelAttribute("sampleModel") LoginForm loginForm, 
      BindingResult bindingResult, Model model) {

    //ログインID
    String userId = loginForm.getUserId();

    //パスワード
    String password = loginForm.getPassword();

    //ログインユーザー取得
    User user = userService.findByIdAndPass(userId, password);

    //未入力チェック
    if (bindingResult.hasErrors()) {
      String errMsg = "未入力の項目があります";
      model.addAttribute("errMsg",errMsg);
      
      return "login";
    }

    //ログインチェック
    if(user == null) {
      //存在しない場合
      String errMsg = "ユーザーIDもしくはパスワードに誤りがあります";
      model.addAttribute("errMsg",errMsg);
      
      return "login";

    }else {
      //存在した場合
      session.setAttribute("loginUser", user);

      return "home";
    }
  }
}
< Form >
ユーザーが画面から入力した内容をinputの名前に応じてフィールドに保存します。
LoginForm(src/main/java/com/example/controller/form/ LoginForm.java)
上記括弧内のディレクトリにLoginForm.javaクラスを新規作成します。
package com.example.controller.form;

import javax.validation.constraints.NotBlank;

public class LoginForm {
  @NotBlank
  private String userId;
  
  @NotBlank
  private String password;

  //アクセサー
  public String getUserId() {
    return userId;
  }

  public void setUserId(String userId) {
    this.userId = userId;
  }

  public String getPassword() {
    return password;
  }

  public void setPassword(String password) {
    this.password = password;
  }
}

まとめ


Springの魅力を感じていただけたでしょうか?

記事の都合上、説明や作成システムが簡素になってしまいましたが、
実際に大量のページを管理するシステムを作る場合
SpringフレームワークのSpring MVCを用いると、各クラスの役割が把握しやすく
DIやAOPによって記述量も減り、書きやすさや見やすさが向上するのが実感できると思います。

また今回は行いませんでしたが、テストに関しても
クラス同士の関係を疎にしてあることで各機能を切り離してできる等
色々なメリットを感じられます。

Spring MVCをうまく活用して一緒にプログラミングを頑張っていきましょう!

投稿者: エムシバ君