Spring入門

Spring フレームワークの自己学習メモです。Udemy、サーチマンさんの「ゼロから環境構築,文法,JSP,レイヤー,依存性,DB接続,Webアプリを一気に学ぶ」の講座をなぞっています。

## 開発環境

- Mac mini (Apple M1 2020) 16GB メモリー
- macOS Sequoia 15.0.1

jdk のインストール後に Pleiades All in One Eclipse の 

## jdk

Java 超初心者ですので　temurin jdk に助けてもらうことにしました。

- ``` https://adoptium.net/temurin/ ``` にアクセスすると、最適な jdk へのリンクを示してくれます
- .pkg のダウンロードが完了したらダブルクリック
  - OpenJDK21U-jdk_aarch64_mac_hotspot_21.0.5_11.pkg

![スクリーンショット 2025-01-12 9.45.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2164727/1ded36de-6348-2548-467e-78e45521a7ba.png)

```
% javac -version
javac 21.0.5
% java -version
openjdk version "21.0.5" 2024-10-15 LTS
OpenJDK Runtime Environment Temurin-21.0.5+11 (build 21.0.5+11-LTS)
OpenJDK 64-Bit Server VM Temurin-21.0.5+11 (build 21.0.5+11-LTS, mixed mode, sharing)
%
```

## Pleiades All in One Eclipse のインストール

リリース2024 Standard Edition を選択しました。

- pleiades-2024-12-java-mac_20241228.dmg

## DB

### MySQL

#### インストール

```
% brew install mysql
```

```
% which mysql
/opt/homebrew/bin/mysql
%
```

#### 起動

```
% mysql.server start
Starting MySQL
.. SUCCESS!
%
```

#### 初期設定

```
% mysql_secure_installation
```

#### 停止

```
% mysql.server stop
Shutting down MySQL
. SUCCESS!
%
```

#### ログイン

```
% mysql -u root -p
Enter password:

(snip)

mysql>
```

#### アンインストール

```
% mysql.server stop
Shutting down MySQL
. SUCCESS!
% brew uninstall mysql
```

#### DB作成

- create database (DB名) default character set utf8mb4 collate utf8mb4_general_ci;

```
mysql> create database user default character set utf8mb4 collate utf8mb4_general_ci;
Query OK, 1 row affected (0.00 sec)

mysql>
```

#### DB一覧

```
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| user               |
+--------------------+
5 rows in set (0.00 sec)

mysql>
```

#### DB切替

```
mysql> use user;
Database changed
mysql>
```

#### create table

```
mysql> create table user (
    -> id int  NOT NULL AUTO_INCREMENT PRIMARY KEY,
    -> name varchar(255),
    -> email varchar(255));
Query OK, 0 rows affected (0.01 sec)

mysql>
```

```
mysql> alter table user add constraint user_u1 unique (email);
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql>
```

#### テーブル一覧

```
mysql> show tables;
+----------------+
| Tables_in_user |
+----------------+
| user           |
+----------------+
1 row in set (0.01 sec)

mysql>
```

#### データ投入

```
mysql> insert into user (name, email) values ('ishi32', 'ishi32@example.com');
Query OK, 1 row affected (0.01 sec)

mysql> select * from user;
+----+--------+--------------------+
| id | name   | email              |
+----+--------+--------------------+
|  1 | ishi32 | ishi32@example.com |
+----+--------+--------------------+
1 row in set (0.00 sec)

mysql>
```

## Spring プロジェクト作成

- 「プロジェクト・エクスプローラ」で右クリック
- [新規] -> [その他] -> [Spring Boot] -> [Spring スターター・プロジェクト (Spring Initializer)] -> [次へ]
  - 名前: app1
  - タイプ: Maven
  - グループ: com.bkiban
  - 説明: User manage
  - パッケージ: com.bkiban.app1
- [次へ] -> (デフォルトで)[次へ] ->  (デフォルトで)[完了]

### pom.xml

<dependencies> に以下を追加

```
	<dependencies>
 (snip)
		<!-- Spring Boot Starter Tomcat -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
			<scope>provided</scope>
		</dependency>

		<!-- JSP support -->
		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-jasper</artifactId>
		</dependency>

		<!-- Spring Boot JDBC -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>

		<!-- MySQL connector -->
		<dependency>
			<groupId>com.mysql</groupId>
			<artifactId>mysql-connector-j</artifactId>
			<scope>runtime</scope>
		</dependency>

  		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.9</version>
		</dependency>

	</dependencies>
```

### ``` src/main/resources/application.properties ```

以下を追加

```
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/user
spring.datasource.username=root
spring.datasource.password=********

spring.mvc.view.prefix=/WEB-INF/
spring.mvc.view.suffix=.jsp
```

### jsp の格納場所を作成

- ``` src/main ``` で右クリック
- [新規] -> [フォルダ]
- 「フォルダ名」に ``` webapp ```
- [完了]
- ``` src/main/webapp ``` で右クリック
- [新規] -> [フォルダ]
- 「フォルダ名」に ``` WEB-INF ```
- [完了]

### 各レイアを作成

- controller
  - ``` src/main/java/com/bkiban/app1 ``` で右クリック
  - [新規] -> [フォルダ]
  - 「フォルダ名」に ```  ```
  - [完了]
  - ``` src/main/webapp ``` で右クリック
  - [新規] -> [フォルダ]
  - 「フォルダ名」に ``` controller ```
  - [完了]
- entity
- repository
- service

### Spring の各レイヤー (パッケージ) の役割

- Controller (HTTPの処理)
ブラウザからの HTTP リクエストを受け取り、必要の応じて Service を呼び出し、その処理結果がある場合は View (JSP や HTML) へ返却する。
- Service (ビジネスロジック)
- Repository (DBアクセス)
- Entity (DBのテーブルに対応)
テーブルごとに、各項目の定義と Getter、Setter をオブジェクトとして定義する。

#### entity

- ``` src/main/java/com/bkiban/app1/entity ``` で右クリック
- [新規] -> [その他] -> [Java] -> [クラス]
  - 名前: User
- [完了]

```
package com.bkiban.app1.entity;

public class User {

	private int id;
	private String name;
	private String email;

	public int getId() {
		return id;
	}

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

	public String getName() {
		return name;
	}

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

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}
}
```

#### repository (インターフェース)

インターフェースには、設計で必要とされるメソッドを全て「抽象メソッド」として定義しておく。こうすることで実装をあらかじめ担保しておく。

- ``` src/main/java/com/bkiban/app1/repository ``` で右クリック
- [新規] -> [その他] -> [Java] -> [インターフェース]
- [次へ]
  - 「名前」: UserRepository
  - [完了]

```
package com.bkiban.app1.repository;

import java.util.List;

import com.bkiban.app1.entity.User;

public interface UserRepository {

	//ユーザリストを取得
	List<User> findAll();

	//idによるユーザ取得
	User findById(int id);

	//ユーザ挿入
	void insertUser(User user);

	//ユーザ更新
	void updateUser(User user);

	//ユーザ削除
	void deleteUser(int id);

}
```

``` ArrayList<User> ``` の ``` User ``` がエラーになっていたら、import が足りていないので ``` User ``` にマウスをあてて、「User をインポートします (com.bkiban.app1.entity)」をクリックする

#### service (インターフェース)

- ``` src/main/java/com/bkiban/app1/service ``` で右クリック
- [新規] -> [その他] -> [Java] -> [インターフェース]
- [次へ]
  - 「名前」: UserService
  - [完了]
- コードは UserRepository.java とほぼ同じ

```
package com.bkiban.app1.service;

import java.util.List;

import com.bkiban.app1.entity.User;

public interface UserService {

	//全ユーザを取得
	List<User> findAll();

	//リクエストからユーザオブジェクトを作成
	User makeUser(User request);

	//ユーザ挿入
	void insertUser(User user);

	//idによるユーザ取得
	User findById(int id);

	//ユーザ更新
	void updateUser(User user);

	//ユーザ削除
	void deleteUser(int id);
	
}
```

#### repository (実装)

- ``` src/main/java/com/bkiban/app1/repository ``` で右クリック
- [新規] -> [その他] -> [Java] -> [クラス]
- [次へ]
  - 「名前」: UserRepositoryImpl
  - 「インターフェース:」で [追加] をクリック
  - 「Search」に User といれると表示される「UserRepository」をクリック
  - [OK]
  - [完了]
- 以下が自動的に生成される

```
package com.bkiban.app1.repository;

import java.util.List;

import com.bkiban.app1.entity.User;

public class UserRepositoryImpl implements UserRepository {

	@Override
	public List<User> findAll() {
		// TODO 自動生成されたメソッド・スタブ
		return null;
	}

	@Override
	public User findById(int id) {
		// TODO 自動生成されたメソッド・スタブ
		return null;
	}

	@Override
	public void insertUser(User user) {
		// TODO 自動生成されたメソッド・スタブ

	}

	@Override
	public void updateUser(User user) {
		// TODO 自動生成されたメソッド・スタブ

	}

	@Override
	public void deleteUser(int id) {
		// TODO 自動生成されたメソッド・スタブ

	}

}
```

- @Repository を追記
- @Autowired
private NamedParameterJdbcTemplate jdbcTemplate; を追記
- 各メソッドを以下のように更新する

```
package com.bkiban.app1.repository;

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.bkiban.app1.entity.User;

@Repository
public class UserRepositoryImpl implements UserRepository {

	@Autowired
	private NamedParameterJdbcTemplate jdbcTemplate;

	@Override
	public List<User> findAll() {
		//SQL文の作成
		final String sql = "select id, name, email from user";

		//SQLの実行
		List<User> userList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<User>(User.class));

		return userList;
	}

	@Override
	public User findById(int id) {
		//SQL文の作成
		final String sql 
		= "select id, name, email from user where id = :id";

		// パラメータの作成
		MapSqlParameterSource param = new MapSqlParameterSource();
		param.addValue("id", id);

		// SQLの実行
		List<User> userList = 
		jdbcTemplate.query(sql, param, new BeanPropertyRowMapper<User>(User.class));

		//リストを判定して戻す
		return userList.isEmpty() ? null : userList.get(0);
	}

	@Override
	public void insertUser(User user) {
		//SQL文の作成
		final String sql = "insert into user(name, email) "
				+ "values(:name,:email)";

		// パラメータの作成
		MapSqlParameterSource param = new MapSqlParameterSource();
		param.addValue("name", user.getName());
		param.addValue("email", user.getEmail());

		// SQLの実行
		jdbcTemplate.update(sql, param);
	}

	@Override
	public void updateUser(User user) {
		//SQL文の作成
		final String sql 
		= "update user set name=:name, email=:email "
		+ "where id=:id";

		// パラメータの作成
		MapSqlParameterSource param = new MapSqlParameterSource();
		param.addValue("id", user.getId());
		param.addValue("name", user.getName());
		param.addValue("email", user.getEmail());

		// SQLの実行
		jdbcTemplate.update(sql, param);
	}

	@Override
	public void deleteUser(int id) {
		//SQL文の作成
		final String sql = "delete from user where id=:id";

		// パラメータの作成
		MapSqlParameterSource param = new MapSqlParameterSource();
		param.addValue("id", id);

		// SQLの実行
		jdbcTemplate.update(sql, param);
	}

}
```

#### service (実装)

- ``` src/main/java/com/bkiban/app1/service ``` で右クリック
- [新規] -> [その他] -> [Java] -> [クラス]
- [次へ]
  - 「名前」: UserServiceImpl
  - 「インターフェース:」で [追加] をクリック
  - 「Search」に User といれると表示される「UserService」をクリック
  - [OK]
  - [完了]
- 以下が自動的に生成される

```
package com.bkiban.app1.service;

import java.util.List;

import com.bkiban.app1.entity.User;

public class UserServiceImpl implements UserService {

	@Override
	public List<User> findAll() {
		// TODO 自動生成されたメソッド・スタブ
		return null;
	}

	@Override
	public User makeUser(User request) {
		// TODO 自動生成されたメソッド・スタブ
		return null;
	}

	@Override
	public void insertUser(User user) {
		// TODO 自動生成されたメソッド・スタブ

	}

	@Override
	public User findById(int id) {
		// TODO 自動生成されたメソッド・スタブ
		return null;
	}

	@Override
	public void updateUser(User user) {
		// TODO 自動生成されたメソッド・スタブ

	}

	@Override
	public void deleteUser(int id) {
		// TODO 自動生成されたメソッド・スタブ

	}

}
```

- @Service を追記
- @Autowired
private NamedParameterJdbcTemplate jdbcTemplate; を追記
- 各メソッドを以下のように更新する

```
package com.bkiban.app1.service;

import java.util.List;

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

import com.bkiban.app1.entity.User;
import com.bkiban.app1.repository.UserRepository;

@Service
public class UserServiceImpl implements UserService {

	@Autowired
	UserRepository userRepository;

	@Override
	public List<User> findAll() {
		return userRepository.findAll();
	}

	@Override
	public User makeUser(User request) {
		//社員オブジェクトの作成
		User user = new User();
		//社員オブジェクトに値を代入
		user.setId(request.getId());
		user.setName(request.getName());
		user.setEmail(request.getEmail());
		//社員オブジェクトを戻す
		return user;
	}

	@Override
	public void insertUser(User user) {
		userRepository.insertUser(user);
	}

	@Override
	public User findById(int id) {
		return userRepository.findById(id);
	}

	@Override
	public void updateUser(User user) {
		userRepository.updateUser(user);
	}

	@Override
	public void deleteUser(int id) {
		userRepository.deleteUser(id);

	}

}
```

#### controller

- ``` src/main/java/com/bkiban/app1/controller ``` で右クリック
- [新規] -> [その他] -> [Java] -> [クラス]
- [次へ]
  - 「名前」: UserController
  - [完了]
- 以下が自動的に生成される

```
package com.bkiban.app1.controller;

public class UserController {

}
```

- @Controller を追記
- @Autowired
private ShainService shainService; を追記
- 各メソッドを以下のように追加する

```
package com.bkiban.app1.controller;

import java.util.List;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
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.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import com.bkiban.app1.entity.User;
import com.bkiban.app1.service.UserService;

@Controller
public class UserController {

	@Autowired
	private UserService userService;

	@GetMapping("/index")
	public String index(Model model) {

		// リスト取得
		List<User> userList = userService.findAll();

		// JSPに渡す
		model.addAttribute("userList", userList);
		// JSPに転送
		return "index";
	}

	@GetMapping("/insert")
	public String insert() {
		// JSPに転送
		return "insert";
	}

	@PostMapping("/insert")
	public String insert(@ModelAttribute User request) {
		//public void insert(@ModelAttribute User request) {

		//System.out.println(shain.getId() + ":" + shain.getName()
		//+ ":" + shain.getSei() + ":" + shain.getNen()
		//+ ":" + shain.getAddress());

		System.out.println(request + ":" + request.getName() + ":" + request.getEmail());
		System.out.println(ToStringBuilder.reflectionToString(request, ToStringStyle.DEFAULT_STYLE));

		//パラメータ値から社員作成
		User user = userService.makeUser(request);

		//DBに挿入
		userService.insertUser(user);

		// リダイレクト
		return "redirect:/index";
	}

	@GetMapping("/update")
	public String update(
			@RequestParam("id") int id,
			Model model) {

		//対象データを取得
		User user = userService.findById(id);

		// JSPに渡す
		model.addAttribute("user", user);

		// JSPに転送
		return "update";
	}

	@PostMapping("/update")
	public String update(@ModelAttribute User request) {

		//パラメータ値から社員作成
		User user = userService.makeUser(request);

		//DB更新
		userService.updateUser(user);

		// リダイレクト
		return "redirect:/index";
	}

	@GetMapping("/delete")
	public String delete(
			@RequestParam("id") int id,
			Model model) {

		//対象データを取得
		User user = userService.findById(id);

		// JSPに渡す
		model.addAttribute("user", user);

		// JSPに転送
		return "delete";
	}

	@PostMapping("/delete")
	public String delete(
			@RequestParam("id") int id) {

		//DBから削除
		userService.deleteUser(id);

		// リダイレクト
		return "redirect:/index";
	}

}
```

public String index(Model model) の Model がエラーになっている。マウスをあてて、「'Model' をインポートします (org.springframework.ui)」をクリックする

### JSP

- ``` src/main/webapp/WEB-INF ``` で右クリック
- [新規] -> [その他] -> [Web] -> [JSP ファイル]
- [次へ]
  - 「名前」: index
  - [完了]
- 以下の内容に更新する

```
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@page import="java.util.ArrayList"%>
<%@page import="com.bkiban.app1.entity.User"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ユーザ一覧</title>
<style>
table {
	border-collapse: collapse; /* セルの境界線を重ねて単線にする */
}

th, td {
	border: 1px solid black; /* セルの境界線のスタイルを設定 */
	padding: 10px;
}
</style>
</head>
<body>
	<h1>ユーザ一覧</h1>

	<table border="1">
		<tr bgcolor="#cccccc">
			<th>ID</th>
			<th>名前</th>
			<th>メールアドレス</th>
			<th>変更</th>
			<th>削除</th>
		</tr>

		<%
		ArrayList<User> userList = (ArrayList<User>) request.getAttribute("userList");
		%>
		<%
		for (User user : userList) {
		%>
		<tr>
			<td><%=user.getId()%></td>
			<td><%=user.getName()%></td>
			<td><%=user.getEmail()%></td>
			<td><a href="update?id=<%=user.getId()%>">変更</a></td>
			<td><a href="delete?id=<%=user.getId()%>">削除</a></td>
		</tr>
		<%
		}
		%>

	</table>
	<p></p>
	<!-- 「ユーザを登録する」ボタン -->
	<form action="insert" method="get">
		<input type="submit" value="ユーザを登録する">
	</form>
</body>
</html>
```

- insert.jsp

```
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8">
<title>社員登録画面</title>
<style>
.form-input {
	width: 100%;
}

.form-table td {
	padding: 5px;
}

.form-table label {
	text-align: right;
}

.form-button {
	margin-top: 10px;
}
</style>
</head>

<body>
	<h1>社員登録画面</h1>
	<form action="insert" method="post">
		<table class="form-table">
			<tr>
				<td><label for="name">名前:</label></td>
				<td><input type="text" id="name" name="name" class="form-input"
					required></td>
			</tr>
			<tr>
				<td><label for="email">メールアドレス:</label></td>
				<td><input type="text" id="email" name="email"
					class="form-input" required></td>
			</tr>
		</table>
		<button type="submit" class="form-button">登録</button>
	</form>
</body>

</html>
```

- update.jsp

```
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@page import="com.bkiban.app1.entity.User"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ユーザ更新画面</title>
<style>
.form-input {
	width: 100%;
}

.form-table td {
	padding: 5px;
}

.form-table label {
	text-align: right;
}

.form-button {
	margin-top: 10px;
}
</style>
</head>
<body>
	<%-- shainから社員情報を取得する --%>
	<%
	User user = (User) request.getAttribute("user");
	%>
	<h1>ユーザ更新画面</h1>
	<form action="update" method="post">
		<table class="form-table">
			<tr>
				<td><label for="id">ID:</label></td>
				<td><%=user.getId()%></td>
			</tr>
			<tr>
				<td><label for="name">名前:</label></td>
				<td><%=user.getName()%></td>
			</tr>
			<tr>
				<td><label for="email">メールアドレス:</label></td>
				<td><input type="text" id="email" name="email"
					value="<%=user.getEmail()%>" class="form-input" required></td>
			</tr>
		</table>
		<button type="submit" class="form-button">更新</button>
		<input type="hidden" name="id" value="<%=user.getId()%>">
		<input type="hidden" name="name" value="<%=user.getName()%>">
	</form>
</body>
</html>
```

- delete.jsp

```
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@page import="com.bkiban.app1.entity.User"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ユーザ削除画面</title>
<style>
.form-input {
	width: 100%;
}

.form-table td {
	padding: 5px;
}

.form-table label {
	text-align: right;
}

.form-button {
	margin-top: 10px;
}
</style>
</head>
<body>
	<%-- shainから社員情報を取得する --%>
	<%
	User user = (User) request.getAttribute("user");
	%>
	<h1>ユーザ削除画面</h1>
	<form action="delete" method="post">
		<table class="form-table">
			<tr>
				<td><label for="id">ID:</label></td>
				<td><%=user.getId()%></td>
			</tr>
			<tr>
				<td><label for="name">名前:</label></td>
				<td><%=user.getName()%></td>
			</tr>
			<tr>
				<td><label for="email">メールアドレス:</label></td>
				<td><%=user.getEmail()%></td>
			</tr>
		</table>
		<button type="submit" class="form-button">削除</button>
		<input type="hidden" name="id" value="<%=user.getId()%>">
	</form>
</body>
</html>
```

### 動作確認

- Bootダッシュボードで app1 を起動
- ブラウザから localhost:8080/index にアクセス

![スクリーンショット 2025-01-04 14.55.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2164727/b7dda909-2bf1-8a07-7031-c05de789fed5.png)

- [ユーザを登録する]　をクリック

![スクリーンショット 2025-01-04 14.57.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2164727/1478774b-d059-c090-74e3-c0bfa99f3a95.png)

- [登録]　をクリック

![スクリーンショット 2025-01-04 14.58.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2164727/d63f5b05-80ec-664f-bc3c-30dbad3bd468.png)

- ID: 1 の [削除] をクリック

![スクリーンショット 2025-01-04 15.00.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2164727/83dc4f7c-63b4-f77f-7d24-66340438a37c.png)

- [削除] をクリック

![スクリーンショット 2025-01-04 15.05.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2164727/50a81b41-7186-0fe1-fdf4-d52df45c5105.png)