# 🌸 Spring Bootで勤怠管理システムを作ってみた【初心者向け解説付き】



こんにちは！今回はSpring Bootを使って、シンプルな勤怠管理システムを作成したので、その紹介と初心者向けの解説をお届けします。


## 🎯 どんなシステム？＆Spring Bootを使うメリット

今回作成したのは、会社の社員の勤務時間を管理する勤怠管理システムです。  
社員を選んで、その人の勤務記録（出勤時間や退勤時間、休暇など）を一覧表示し、勤務時間の入力もできるシンプルなWebアプリです。

---

### Spring Bootを使うメリット

Spring BootはJavaでWebアプリや業務システムを作るときにとても便利なフレームワークです。  

- **設定が簡単**  
  複雑なXML設定がほぼ不要で、少ないコードで起動できます。  
- **開発がスピーディー**  
  Webサーバー（Tomcat）が内蔵されているので、別途サーバーを用意しなくてもすぐに動作確認ができます。  
- **豊富な機能が標準搭載**  
  データベース接続、REST API、セキュリティなど、よく使う機能を簡単に追加可能です。  
- **コミュニティが活発**  
  問題解決の情報やプラグインが充実しているので、困ったときも助かります。
- **コーディングが楽になる**  
  Spring Bootは自動設定（Auto Configuration）や便利なアノテーションで、開発者が書くコード量を減らしてくれます。  
  例えば、データベース接続やWebの設定を自動で行ってくれるので、初めての人でも複雑な設定ファイルに悩まされることが少なくなります。  
  そのため、業務ロジックやUIの実装に集中でき、開発効率が大幅にアップします。

初心者でも比較的取り組みやすく、実務で使われることも多いので、今回の勤怠管理システム開発にSpring Bootを採用しました。


## 🎯 どんなシステム？

今回作成したのは「勤怠管理システム」です。  
社員が自分の勤務時間（出勤・退勤）を記録し、過去の勤怠情報を確認できるシンプルなシステムです。  
社員の選択画面から個別の勤怠記録画面へ遷移し、新しい勤務時間の入力も行えます。  
備考欄には「通常出勤」や「遅刻」などの勤務状態も登録可能です。  
Spring Bootを使い、Webアプリケーションとして動作します。

---

## 📁 パッケージ構成（構造）

- `jp.co.attendance.controller`  
  → Webからのリクエストを受け取るコントローラ

- `jp.co.attendance.service`  
  → ビジネスロジックを扱うサービス層のインターフェースと実装

- `jp.co.attendance.dao` / `jp.co.attendance.dao.impl`  
  → データベース操作を担当するDAO層

- `jp.co.attendance.entity`  
  → データベースのテーブル構造を表現するエンティティクラス

- JSPファイルは  
  `src/main/webapp/WEB-INF/views` 配下に配置し、画面の表示を担当しています。  

このように役割ごとにフォルダを分けて整理しています。

---

## 🚀 起動クラス - AttendanceApplication.java
ここから各リソースについて解説していきます。
ここでは起動クラスのAttendanceApplication.javaを解説します。
`@SpringBootApplication` アノテーションを付けることで、自動設定やコンポーネントスキャンが有効になります。  
`main`メソッドで `SpringApplication.run()` を呼び出して、アプリケーションを起動します。

```java
package jp.co.attendance;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class AttendanceApplication {

    public static void main(String[] args) {
        SpringApplication.run(AttendanceApplication.class, args);
    }
}

```



## 🎮 コントローラ - EmployeeController.java

EmployeeControllerは、ユーザーのリクエストを受け取り、サービス層を通じて処理を実行します。  
社員一覧表示、特定社員の勤怠情報表示、新しい勤怠記録の登録などを担当しています。  
例えば、社員の一覧画面を表示したり、勤怠記録を登録したりする処理が含まれます。



```java
package jp.co.attendance.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import jp.co.attendance.entity.Employee;
import jp.co.attendance.entity.TimeRecord;
import jp.co.attendance.service.EmployeeService;

import java.util.List;

@Controller
public class EmployeeController {

    private final EmployeeService service;

    public EmployeeController(EmployeeService service) {
        this.service = service;
    }

    @GetMapping("/")
    public String selectEmployee(Model model) {
        model.addAttribute("employees", service.getAllEmployees());
        return "select_employee";
    }

    @GetMapping("/employee/{id}")
    public String viewTimeRecords(@PathVariable int id, Model model) {
        model.addAttribute("employee", service.getEmployeeById(id));
        model.addAttribute("records", service.getTimeRecordsByUserId(id));
        model.addAttribute("recordForm", new TimeRecord());
        return "time_record";
    }

    @PostMapping("/employee/{id}/save")
    public String saveTimeRecord(@PathVariable int id, @ModelAttribute TimeRecord record) {
        record.setUserId(id);
        service.saveTimeRecord(record);
        return "redirect:/employee/" + id;
    }
}
```


---

## 🧠 サービス層 - EmployeeService.java / EmployeeServiceImpl.java

サービス層はビジネスロジックを担当します。  
EmployeeServiceはインターフェースで、社員情報や勤怠記録の取得・登録メソッドを定義しています。  
EmployeeServiceImplでは実際にDAOを呼び出し、データの取得や保存を行います。  
コントローラとDAOの間に立ち、処理の橋渡しをしています。

```java
package jp.co.attendance.service;

import java.util.List;
import jp.co.attendance.entity.Employee;
import jp.co.attendance.entity.TimeRecord;

public interface EmployeeService {
    List<Employee> getAllEmployees();
    Employee getEmployeeById(int userId);
    List<TimeRecord> getTimeRecordsByUserId(int userId);
    void saveTimeRecord(TimeRecord record);
}

```

```java

package jp.co.attendance.service.impl;

import org.springframework.stereotype.Service;
import jp.co.attendance.dao.EmployeeDao;
import jp.co.attendance.entity.Employee;
import jp.co.attendance.entity.TimeRecord;
import jp.co.attendance.service.EmployeeService;

import java.util.List;

@Service
public class EmployeeServiceImpl implements EmployeeService {

    private final EmployeeDao dao;

    public EmployeeServiceImpl(EmployeeDao dao) {
        this.dao = dao;
    }

    @Override
    public List<Employee> getAllEmployees() {
        return dao.getAllEmployees();
    }

    @Override
    public Employee getEmployeeById(int userId) {
        return dao.getEmployeeById(userId);
    }

    @Override
    public List<TimeRecord> getTimeRecordsByUserId(int userId) {
        return dao.getTimeRecordsByUserId(userId);
    }

    @Override
    public void saveTimeRecord(TimeRecord record) {
        dao.saveTimeRecord(record);
    }
}
```
---

## 🗃 DAO層 - EmployeeDaoImpl.java

DAO層はデータベースとのやり取りを担当します。  
EmployeeDaoImplは社員情報や勤怠記録をSQLで取得・登録する処理を実装しています。  
SpringのNamedParameterJdbcTemplateを使い、SQLを実行して結果をエンティティにマッピングします。  
具体的には、社員一覧取得、社員情報取得、勤怠記録一覧取得、勤怠記録の登録処理があります。

```java

package jp.co.attendance.dao;

import java.util.List;
import jp.co.attendance.entity.Employee;
import jp.co.attendance.entity.TimeRecord;

public interface EmployeeDao {
    List<Employee> getAllEmployees();
    Employee getEmployeeById(int userId);
    List<TimeRecord> getTimeRecordsByUserId(int userId);
    void saveTimeRecord(TimeRecord timeRecord);
}



```


```java

package jp.co.attendance.dao.impl;

import jp.co.attendance.dao.EmployeeDao;
import jp.co.attendance.entity.Employee;
import jp.co.attendance.entity.TimeRecord;

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 java.util.List;

@Repository
public class EmployeeDaoImpl implements EmployeeDao {

    private static final String SELECT_ALL_EMPLOYEES = "SELECT * FROM employee_table ORDER BY user_id";
    private static final String SELECT_EMPLOYEE_BY_ID = "SELECT * FROM employee_table WHERE user_id = :userId";

    private static final String SELECT_TIME_RECORDS_BY_USER_ID =
    	    "SELECT id, user_id, date, start_time, end_time AS finish_time, bikou " +
    	    "FROM time_record WHERE user_id = :userId ORDER BY `date`";


    private static final String INSERT_TIME_RECORD =
    	    "INSERT INTO time_record (user_id, date, start_time, end_time, bikou) " +
    	    "VALUES (:userId, :date, :startTime, :finishTime, :bikou)";


    private final NamedParameterJdbcTemplate jdbcTemplate;

    public EmployeeDaoImpl(NamedParameterJdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public List<Employee> getAllEmployees() {
        MapSqlParameterSource param = new MapSqlParameterSource(); // 空でもOK
        List<Employee> resultList = jdbcTemplate.query(
            SELECT_ALL_EMPLOYEES,
            param,
            new BeanPropertyRowMapper<>(Employee.class)
        );
        return resultList;
    }

    @Override
    public Employee getEmployeeById(int userId) {
        MapSqlParameterSource param = new MapSqlParameterSource();
        param.addValue("userId", userId);

        List<Employee> resultList = jdbcTemplate.query(
            SELECT_EMPLOYEE_BY_ID,
            param,
            new BeanPropertyRowMapper<>(Employee.class)
        );
        return resultList.isEmpty() ? null : resultList.get(0);
    }

	    @Override
	    public List<TimeRecord> getTimeRecordsByUserId(int userId) {
	        MapSqlParameterSource param = new MapSqlParameterSource();
	        param.addValue("userId", userId);

	        return jdbcTemplate.query(
	            SELECT_TIME_RECORDS_BY_USER_ID,
	            param,
	            new BeanPropertyRowMapper<>(TimeRecord.class)
	        );


	    }

    @Override
    public void saveTimeRecord(TimeRecord timeRecord) {
        MapSqlParameterSource param = new MapSqlParameterSource();
        param.addValue("userId", timeRecord.getUserId());
        param.addValue("date", timeRecord.getDate());
        param.addValue("startTime", timeRecord.getStartTime());
        param.addValue("finishTime", timeRecord.getFinishTime());
        param.addValue("bikou", timeRecord.getBikou());  //

        jdbcTemplate.update(INSERT_TIME_RECORD, param);
    }

    }



```

---

## 👤 エンティティ - Employee.java / TimeRecord.java

エンティティはデータの構造を表すクラスです。  
Employee.javaは社員のID・名前・部署情報を持っています。  
TimeRecord.javaは勤怠記録を表し、ID、社員ID、勤務日、出勤時間、退勤時間、備考などの情報を保持します。  
データベースのレコードをオブジェクトとして扱うためのクラスです。

```java
package jp.co.attendance.entity;

public class Employee {
    private int userId;
    private String name;
    private String department;

    public Employee() {

    }

    public Employee(Integer userId, String name, String department) {
        this.userId = userId;
        this.name = name;
        this.department = department;
    }

    public Integer getUserId() {
        return userId;
    }
    public void setUserId(Integer userId) {
        this.userId = userId;
    }


    public String getName() {
        return name;
    }

    public void setName(String Name) {
        this.name = Name;
    }


    public String getdepartment() {
        return department;
    }

    public void setdepartment(String department) {
        this.department = department;
    }

}


```


```java
package jp.co.attendance.entity;

import java.time.LocalDate;
import java.time.LocalTime;

public class TimeRecord {
    private int id;
    private int userId;
    private LocalDate date;
    private LocalTime startTime;
    private LocalTime finishTime;
    private String bikou;

    public TimeRecord() {

    }


    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getUserId() {
        return userId;
    }

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

    public LocalDate getDate() {
        return date;
    }

    public void setDate(LocalDate date) {
        this.date = date;
    }

    public LocalTime getStartTime() {
        return startTime;
    }

    public void setStartTime(LocalTime startTime) {
        this.startTime = startTime;
    }

    public LocalTime getFinishTime() {
        return finishTime;
    }

    public void setFinishTime(LocalTime finishTime) {
        this.finishTime = finishTime;
    }

    public String getBikou() {
        return bikou;
    }

    public void setBikou(String bikou) {
        this.bikou = bikou;
    }
}

```


## 🖥 JSPビュー - select_employee.jsp / time_record.jsp

画面表示を担当するJSPファイルです。  

- **select_employee.jsp**  
  社員一覧のドロップダウンメニューを表示し、選択された社員の勤怠画面へ遷移します。  
  JSTLの<c:forEach>タグを使って、社員リストをループ表示しています。

``` jsp

<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head><meta charset="UTF-8"><title>勤務入力</title></head>
<body>
    <h2>勤務時間</h2>
    <form action="" method="get">
        <select onchange="location.href='/employee/' + this.value">
            <option value="">--選択--</option>
            <c:forEach var="emp" items="${employees}">
                <option value="${emp.userId}">${emp.name}（${emp.department}）</option>
            </c:forEach>
        </select>
    </form>
</body>
</html>

```


- **time_record.jsp**  
  選択した社員の勤怠記録一覧をテーブルで表示し、新しい勤務時間を入力するフォームを提供します。  
  日付や開始・終了時刻、備考を入力してPOST送信し、勤怠データを登録します。

```jsp

<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
    <meta charset="UTF-8">
    <title>勤務記録</title>
</head>
<body>
    <h2>${employee.name}（${employee.department}）の勤務</h2>
    <table border="1">
        <tr><th>日付</th><th>開始</th><th>終了</th><th>備考</th></tr>
        <c:forEach var="r" items="${records}">
            <tr>
                <td>${r.date}</td>
                <td>${r.startTime}</td>
                <td>${r.finishTime}</td>
                <td>${r.bikou}</td>
            </tr>
        </c:forEach>
    </table>

    <h3>勤務時間入力</h3>
	<form action="/employee/${employee.userId}/save" method="post">
	    <input type="hidden" name="userId" value="${employee.userId}">
	    日付: <input type="date" name="date" required><br>
	    開始: <input type="time" name="startTime" required><br>
	    終了: <input type="time" name="finishTime" required><br>
    	備考:
    <select name="bikou" required>
        <option value="通常出勤">通常出勤</option>
        <option value="時差出勤">時差出勤</option>
        <option value="遅刻">遅刻</option>
        <option value="早退">早退</option>
        <option value="休暇">休暇</option>
    </select><br>

	    <button type="submit">登録</button>
	</form>
</body>
</html>

```

どちらのJSPもUTF-8の文字コード設定がされており、  
`WEB-INF/views` 配下に配置してSpringのViewResolverで呼び出される構成です。

---

## 🗄 SQLの準備 - データベースにテーブルを作ろう！

この勤怠管理システムを動かすには、まずデータベースに必要なテーブルを作成しておく必要があります。今回はMySQLを想定していますが、他のRDB（PostgreSQLなど）でも構文はほぼ同じです。

---

### 💾 使用するテーブルは2つ！

このシステムでは、以下の2つのテーブルを使います：

| テーブル名         | 内容                      |
|-------------------|---------------------------|
| `employee_table`  | 社員の基本情報を保存       |
| `time_record`     | 勤務時間の記録を保存       |

---

### 🧱 テーブル作成SQL

以下のSQLを実行すれば、システムが必要とするテーブルが作成されます。

#### `employee_table`（社員情報）

```sql
CREATE TABLE employee_table (
    user_id INT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    department VARCHAR(100) NOT NULL
);
```
---

#### `time_record`（勤怠情報）

```sql
CREATE TABLE time_record (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    date DATE NOT NULL,
    start_time TIME NOT NULL,
    end_time TIME NOT NULL,
    bikou VARCHAR(50),
    FOREIGN KEY (user_id) REFERENCES employee_table(user_id)
);

```

###サンプルデータ
また、サンプルとして以下をsql上で打ち込んでください

```
INSERT INTO employee_table (user_id, name, department) VALUES
(1, '田中 太郎', '営業部'),
(2, '鈴木 花子', '開発部'),
(3, '佐藤 次郎', '総務部');
```

```
INSERT INTO time_record (user_id, date, start_time, end_time, bikou) VALUES
(1, '2025-06-10', '09:00:00', '18:00:00', '通常出勤'),
(2, '2025-06-10', '09:30:00', '18:30:00', '時差出勤'),
(3, '2025-06-10', '10:00:00', '17:00:00', '遅刻');

```



事前準備は以上となります。
---

### 実際に動かしてみた
では、実際に動かしてみましょう。
まず、spring bootを起動させて
ブラウザを開いてhttp://localhost:8080/　
にアクセスすると
下記のページが表示されます

![初期画面.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4047037/0b91c13a-d75b-439a-80d0-1a443d46d0fe.png)

次に、田中太郎さんを選択してみます。
下記のように田中さんの勤務状況が出ます。

![社員1勤務状況.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4047037/033a54a1-dfd5-4022-961f-43c0199f469a.png)



では勤怠を入力しましょう。
6/11に早退をしたと想定して入力します。
![社員1勤務状況②.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4047037/18df3bb4-5d9c-4714-a4c6-a8cb10b51e69.png)


登録ボタンを押すとその情報がDBに登録され、
その内容が画面に表示されます
![社員1勤務状況④.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4047037/746d3a52-52c0-4f26-afe4-aba0549dedff.png)


---

## ✅ まとめ

今回はSpring Bootを使って、シンプルな勤怠管理システムを作成しました。  
- Spring Bootの自動設定とDIを活用し、コントローラ、サービス、DAO、エンティティを分けて設計  
- JSPを使ってWeb画面を構築し、社員の勤怠情報の一覧表示と入力機能を実装  
- パッケージ構成を機能別に分けて管理し、わかりやすく保守しやすいコードに  

初心者の方でも取り組みやすいよう、シンプルな設計を心がけました。  
この経験をベースに認証機能や画面の改良、API化など拡張していくのもおすすめです！  
ぜひチャレンジしてみてくださいね。