Spring Bootを使ってみた！(家計簿アプリ)

## 現状のスキル感
- エンジニア歴2年程度
- Servlet, JSPを研修を通して理解できたレベル感。
- VB.NET, Pythonを多少かじった程度。
- SpringBootはほぼ初見。

## きっかけ
- SpringBootを試しで使ってみたので、備忘録として掲載します。
- 制作日数2～3日程で質の担保はできませんので、ご了承下さい。
- 見た目や例外処理、バリデーションチェックなどはほぼ実装していません。
- 内容について誤りや改善点などあればご教授頂けると幸いです。

## 家計簿アプリのイメージスライド
<iframe src="https://docs.google.com/presentation/d/e/2PACX-1vRBbkcyDdhNoKSz0ZmqLiQkNHRUnN75COjybqy3SCiMB4cZqxobIG5qOCqrD3L-ZvIviYkC-41JSoIC/embed?start=false&loop=false&delayms=3000" frameborder="0" width="960" height="440" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true"></iframe>

- 家計簿アプリ
- CRUD処理を実装
- 表示の単位は一覧、年別、年別月別を用意
- 登録、更新、削除は一般的な内容

### ソースコード
https://github.com/takaaki18/house-hold-account

## TOP画面のHTML
```html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
	<meta charset="UTF-8">
	<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
	<link th:href="@{/css/styles.css}" rel="stylesheet">
	<title>家計簿</title>
</head>

<body>
	<div th:replace="~{block/header::headerAccount}"></div>

	<h1>家計簿一覧画面</h1>
	<table class="table table-striped">
		<tr>
			<th>No</th>
			<th>日時</th>
			<th>種別</th>
			<th>品名</th>
			<th>金額</th>
		</tr>

		<tr th:each="account, accountStat : ${list}">
			<td th:text="${accountStat.count}"></td>
			<td th:text="${account.date}"></td>
			<td th:if="${account.type == 1}">食費</td>
			<td th:if="${account.type == 2}">趣味</td>
			<td th:if="${account.type == 3}">教養</td>
			<td th:text="${account.item}"></td>
			<td th:text="${account.price} + '円'"></td>
		</tr>
	</table>

	<table class="table table-striped">
		<tr>
			<th>合計金額</th>
			<td th:text="${totalPrice} + '円'"></td>
		<tr>
	</table>

	<script th:src="@{/js/bootstrap.bundle.min.js}"></script>
</body>
</html>
```
- トップページアクセス時にデータベースから家計簿情報を取得、表示。
- cssはbootstrapを使用しています。ヘッダーやテーブルの装飾など。
- thymeleafを利用してth:eachタグでリストの情報を表示している。
- No情報は画面表示時に1から採番しています。(DBはAUTO_INCREMENTの為。)

## Controller
```java
package com.example.demo.controller;

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.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.example.demo.entity.Account;
import com.example.demo.service.AccountService;

/**
 * @author takaaki
 * コントローラークラス
 */
@Controller
@RequestMapping("/account")
public class AccountController {

	private final AccountService service;

	@Autowired
	public AccountController(AccountService service) {
		this.service = service;
	}

	// 全件表示を行う
	@GetMapping
	public String account(Model model) {

		List<Account> list = service.findAll();
		int totalPrice = service.getTotalPrice();
		model.addAttribute("list", list);
		model.addAttribute("totalPrice", totalPrice);
		return "account/index";
	}

	// 新規登録画面へ遷移
	@GetMapping("/insert")
	public String goInsert() {

		return "account/insert";
	}

	// 新規登録画面へ遷移
	@PostMapping("/insert")
	public String insert(Model model, Account account) {

		account = service.insertAccount(account);
		model.addAttribute("account", account);
		return "account/insertComplete";
	}

	// 削除画面へ遷移
	@GetMapping("/delete")
	public String goDelete(Model model) {

		List<Account> list = service.findAll();
		int totalPrice = service.getTotalPrice();
		model.addAttribute("list", list);
		model.addAttribute("totalPrice", totalPrice);
		return "account/delete";
	}

	// 削除確認画面へ遷移
	@GetMapping("/deleteConfirm")
	public String deleteConfirm(Model model, @RequestParam String id) {

		Account account = service.findAcountById(id);
		model.addAttribute("account", account);
		return "account/deleteConfirm";
	}

	// 削除処理を行う
	@PostMapping("/delete")
	public String delete(Model model, @RequestParam String id) {

		Account account = service.findAcountById(id);
		model.addAttribute("account", account);
		service.deleteAcountById(id);
		return "account/deleteComplete";
	}

	// 更新画面へ遷移
	@GetMapping("/update")
	public String goUpdate(Model model) {

		List<Account> list = service.findAll();
		int totalPrice = service.getTotalPrice();
		model.addAttribute("list", list);
		model.addAttribute("totalPrice", totalPrice);
		return "account/update";
	}

	// 更新入力画面へ遷移
	@GetMapping("/updateInput")
	public String updateInput(Model model, @RequestParam String id) {

		Account account = service.findAcountById(id);
		model.addAttribute("account", account);
		return "account/updateInput";
	}

	// 更新確認画面へ遷移
	@PostMapping("/updateConfirm")
	public String updateConfirm(Model model, Account account) {

		model.addAttribute("account", account);
		return "account/updateConfirm";
	}

	// 更新処理を行う
	@PostMapping("/update")
	public String update(Model model, Account account) {

		model.addAttribute("account", account);
		service.updateAccount(account);
		return "account/updateComplete";
	}

	// 年別集計画面へ遷移
	@GetMapping("/findByYear")
	public String goFindByYear() {

		return "account/findByYear";
	}

	// 年別集計処理を行う
	@PostMapping("/findByYear")
	public String findByYear(Model model, @RequestParam String year) {

		List<Account> list2 = service.findAccountByYear(year);
		int totalPrice = service.getTotalPrice();
		model.addAttribute("totalPrice", totalPrice);
		model.addAttribute("year", year);
		model.addAttribute("list", list2);
		return "account/findByYear";
	}

	// 年別月別集計画面へ遷移
	@GetMapping("/findByYearAndMonth")
	public String goFindByYearAndMonth() {

		return "account/findByYearAndMonth";
	}

	// 年別月別集計処理を行う
	@PostMapping("/findByYearAndMonth")
	public String findByYearAndMonth(Model model, @RequestParam String year, @RequestParam String month) {

		List<Account> list2 = service.findAccountByYearAndMonth(year, month);
		int totalPrice = service.getTotalPrice();
		model.addAttribute("totalPrice", totalPrice);
		model.addAttribute("year", year);
		model.addAttribute("month", month);
		model.addAttribute("list", list2);
		return "account/findByYearAndMonth";
	}
}
```
- 全ての画面への遷移を担当。
- DBにアクセスする為にサービスクラスに処理を投げている。

## Service
```java
package com.example.demo.service;

import java.util.Calendar;
import java.util.List;

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

import com.example.demo.dao.AccountDAO;
import com.example.demo.entity.Account;

/**
 * @author takaaki
 * ロジック担当
 */
@Service
public class AccountService {

	private final AccountDAO dao;

	// 合計金額格納用
	private int totalPrice;

	@Autowired
	public AccountService(AccountDAO dao) {
		this.dao = dao;
	}

	// 全件検索
	public List<Account> findAll() {

		List<Account> list = dao.findAll();
		totalPrice = 0;
		for (Account account : list) {
			totalPrice += account.getPrice();
		}
		return list;
	}

	// 新規登録
	public Account insertAccount(Account account) {

		dao.insertAccount(account);
		return account;
	}

	// 1件検索
	public Account findAcountById(String id) {

		return dao.findAcountById(id);
	}

	// 1件削除
	public void deleteAcountById(String id) {

		dao.deleteAccountById(id);
	}

	// 1件更新
	public void updateAccount(Account account) {

		dao.updateAccount(account);
	}

	// 年別処理を行う
	public List<Account> findAccountByYear(String year) {

		String startDate = year + "-01-01";
		String endDate = year + "-12-31";
		List<Account> list = dao.findAccountByYear(startDate, endDate);
		totalPrice = 0;
		for (Account account : list) {
			totalPrice += account.getPrice();
		}
		return list;

	}

	// 年別月別処理を行う
	public List<Account> findAccountByYearAndMonth(String year, String month) {

		int yearInt = Integer.parseInt(year);
		int monthInt = Integer.parseInt(month);
		String startDate = year + "-" + month + "-" + "01";
		Calendar calendar = Calendar.getInstance();
		calendar.set(Calendar.YEAR, yearInt);
        calendar.set(Calendar.MONTH, monthInt - 1);
        int result = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
        String endDate = year + "-" + month + "-" + result;
        List<Account> list = dao.findAccountByYearAndMonth(startDate, endDate);
        totalPrice = 0;
		for (Account account : list) {
			totalPrice += account.getPrice();
		}
		return list;

	}

	public int getTotalPrice() {
		return totalPrice;
	}

	public void setTotalPrice(int totalPrice) {
		this.totalPrice = totalPrice;
	}
}
```
- DAOクラスにアクセスしてCRUD処理を実行する。
- 年別処理は検索範囲を「入力した月-01-01」から「入力した月-12-31」に設定。
- 年別月別処理は検索範囲を動的に取得。末尾の日が年度によって異なる為。

## Repository
```java
package com.example.demo.dao;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import com.example.demo.entity.Account;

/**
 * @author takaaki
 * DAOクラス
 */
@Repository
public class AccountDAO {

	private final JdbcTemplate jdbcTemplate;

	@Autowired
	public AccountDAO(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
	}

	// 全件検索処理
	public List<Account> findAll() {

		String sql = "SELECT id, date, type, item, price FROM account order by date, id";
		List<Map<String, Object>> resultList = jdbcTemplate.queryForList(sql);
		List<Account> list = new ArrayList<>();

		for (Map<String, Object> result : resultList) {

			Account account = new Account();
			account.setId((int) result.get("id"));
			Date date = (Date) result.get("date");
			SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
			String strDate = dateFormat.format(date);
			account.setDate(strDate);
			account.setType((int) result.get("type"));
			account.setItem((String) result.get("item"));
			account.setPrice((int) result.get("price"));
			list.add(account);
		}
		return list;
	}

	// 新規登録処理
	public void insertAccount(Account account) {

		jdbcTemplate.update("insert into account (date, type, item, price) values (?,?,?,?)",
				account.getDate(), account.getType(), account.getItem(), account.getPrice());
	}

	// ID検索処理
	public Account findAcountById(String id) {

		String sql = "SELECT id, date, type, item, price FROM account where id = ?";
		Map<String, Object> result = jdbcTemplate.queryForMap(sql, id);

		Account account = new Account();
		account.setId((int) result.get("id"));
		Date date = (Date) result.get("date");
		SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
		String strDate = dateFormat.format(date);
		account.setDate(strDate);
		account.setType((int) result.get("type"));
		account.setItem((String) result.get("item"));
		account.setPrice((int) result.get("price"));

		return account;
	}

	// 削除処理
	public void deleteAccountById(String id) {

		jdbcTemplate.update("delete from account where id = ?", id);
	}

	// 更新処理
	public void updateAccount(Account account) {

		jdbcTemplate.update("update account set date = ?, type = ?, item = ?, price = ? where id = ?",
				account.getDate(), account.getType(), account.getItem(), account.getPrice(), account.getId());
	}

	// 年別検索処理
	public List<Account> findAccountByYear(String startDate, String endDate) {

		String sql = "SELECT id, date, type, item, price FROM account "
				+ "where date between ? and ? order by date, id";
		List<Map<String, Object>> resultList = jdbcTemplate.queryForList(sql,
				startDate, endDate);
		List<Account> list = new ArrayList<>();

		for (Map<String, Object> result : resultList) {

			Account account = new Account();
			account.setId((int) result.get("id"));
			Date date = (Date) result.get("date");
			SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
			String strDate = dateFormat.format(date);
			account.setDate(strDate);
			account.setType((int) result.get("type"));
			account.setItem((String) result.get("item"));
			account.setPrice((int) result.get("price"));
			list.add(account);
		}
		return list;
	}

	// 年別月別検索処理
	public List<Account> findAccountByYearAndMonth(String startDate, String endDate) {

		String sql = "SELECT id, date, type, item, price FROM account "
				+ "where date between ? and ? order by date, id";
		List<Map<String, Object>> resultList = jdbcTemplate.queryForList(sql,
				startDate, endDate);
		List<Account> list = new ArrayList<>();

		for (Map<String, Object> result : resultList) {

			Account account = new Account();
			account.setId((int) result.get("id"));
			Date date = (Date) result.get("date");
			SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
			String strDate = dateFormat.format(date);
			account.setDate(strDate);
			account.setType((int) result.get("type"));
			account.setItem((String) result.get("item"));
			account.setPrice((int) result.get("price"));
			list.add(account);
		}
		return list;
	}
}
```
- 購入日をEntityがString型、DBはDate型で設計したのでキャスト処理が必要。

### 全部を書き出すには分量が多すぎるので、次回以降に処理の詳細を記載していこうと思います。
https://qiita.com/takaakitanaka_cre-co/items/3d85a161a3ad2edb418f