0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

レシピApp開発備忘録(DBの用意・サービスクラスの用意・CRUD基礎)

Last updated at Posted at 2025-05-24

データベース

エンティティクラス(テーブルに相当)を作成

Recipe.java
package com.example.demo;

import jakarta.persistence.Column; //追記
import jakarta.persistence.Entity; //追記
import jakarta.persistence.GeneratedValue; //追記
import jakarta.persistence.GenerationType; //追記
import jakarta.persistence.Id; //追記
import jakarta.persistence.Table; //追記

@Entity
@Table(name="recipes")
public class Recipe {
	@Id
	@GeneratedValue(strategy=GenerationType.AUTO)
	@Column
	private long id;
	
	@Column(length=50,nullable=false)
	private String name; //レシピと料理名は一対一なのでリレーションなし
	
	
	
	@Column
	private String comment; //レシピとコメントの記述は一対多なのでdescriptionsテーブルにrecipe_id
	
	@Column
	private int cooking_time; //レシピと料理時間は一対一なのでリレーションなし
	
	@Column
	private int servings; //レシピと人数は一対一なのでリレーションなし
	
	@Column
	private int user_id; //レシピと投稿ユーザーは多対一なのでrecipesテーブルがuser_idをもつ
	
	@Column
	private String main_image; //レシピとメイン画像は一対一なのでリレーションなし
	
	public long getId() {
		return id;
	}
	public void setId(long id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getComment() {
		return comment;
	}
	public void setComment(String comment) {
		this.comment = comment;
	}
	public int getCookingTime() {
		return cooking_time;
	}
	public void setCookingTime(int cooking_time) {
		this.cooking_time = cooking_time;
	}
	public int getServings() {
		return servings;
	}
	public void setServings(int servings) {
		this.servings = servings;
	}
	public int getUser() {
		return user_id;
	}
	public void setUser(int user_id) {
		this.user_id = user_id;
	}
	public String getMainImg() {
		return main_image;
	}
	public void setMainImg(String MainImg) {
		this.main_image = MainImg;
	}
}

対応するDBを作成

方法は二つ

  • MySQLなどで手動でSQLかいてテーブル作成
  • pring Boot に自動でテーブルを作らせる(推奨)
    以下を追記
application.properties
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://localhost:3306/your_db_name?useSSL=false&serverTimezone=UTC
spring.datasource.username=your_user
spring.datasource.password=your_pass
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

ただし

  • MySQLに接続できる状態になっていること(DBとユーザーが作られている)が前提
  • 本番運用では ddl-auto=none か validate を推奨

リポジトリ階層でエラー

アプリ起動→エラーでた!
原因はリポジトリの階層

com.example.demo                      ← 起点(@SpringBootApplicationがある)
 ├── HelloController.java
 ├── Recipe.java
com.example.recipe_appRepository      ← リポジトリ(別階層)
 ├── CookingRepository.java

→これだとrecipe_appRepository が demo パッケージの外にあるので、Spring Boot はデフォルトのスキャンでは見つけてくれない
→CookingRepository を demo 配下に移動して解決

サービスクラス

コントローラにはリクエストやレスポンス対応、サービスクラスにはDB操作などビジネスロジックを集中させる

RecipeService.java
package com.example.demo.Services;
import java.util.Optional;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.example.demo.Recipe;
import com.example.demo.repository.CookingRepository;

@Service
public class RecipeService {
private final CookingRepository repository;
public RecipeService(CookingRepository repository) {
	this.repository = repository;
}
public Optional<Recipe> findById(long id) {
	//リポジトリの既存メソッドを引数渡して呼び出す
	return repository.findById(id);
}
@Transactional
public void deleteById(long id) {
	repository.deleteById(id);
}

}

Servicesパッケージの中にサービスクラス作成

コンストラクタインジェクション

Spring Boot はアプリ起動時に次のことをする

  • @Service@Repository, @Controller(Beanとして登録されたって証) などが付いたクラスを見つける
  • それらを自動でインスタンス化(new みたいなこと)する(この作成されたインスタンスを「Bean(ビーン)」と呼ぶ)
  • それを使いたい場所に「注入(インジェクション)」する→RecipeService recipeService という引数を見て、Springは「あっ、RecipeService のインスタンス(Bean)なら持ってるから、ここに渡してあげよう」ってする

今までは@Autowiredを使って@RepositoryがついたCookingRepositoryのインスタンスを自動的に注入してた
→コンストラクタインジェクションに変更
→final でプロパティ(フィールド)を宣言しておいて、コンストラクタで注入するのが今の主流の書き方
例:DeleteControllerとサービスクラスを分離したときのインジェクション

DeleteController.java
package com.example.demo;
import java.util.List;

import java.util.Optional;

import jakarta.transaction.Transactional;
import jakarta.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import com.example.demo.Recipe;
import com.example.demo.repository.CookingRepository;
import org.springframework.web.bind.annotation.RequestParam;
import com.example.demo.Services.RecipeService;

@Controller
public class DeleteController {

    //コンストラクタインジェクションでRecipeServiceを扱えるようにする
	private final RecipeService recipeService;
	public DeleteController(RecipeService recipeService) {
		this.recipeService = recipeService;
	}
	
	//特定のIDのリソースを画面表示
	@RequestMapping(value="/delete/{id}",method= RequestMethod.GET)
	public ModelAndView delete(@PathVariable int id,ModelAndView mav) {
		mav.setViewName("delete");
		mav.addObject("msg","どのレシピを削除しますか?");
		Optional<Recipe> data = recipeService.findById((long)id);
		if(data.isPresent()) {
			mav.addObject("formModel",data.get());
		}else if(data.isEmpty()){
			mav.addObject("formModel",null);
			mav.addObject("message","データがみつかりません");
		}
		return mav;
	}
	
	@PostMapping("/delete_clicked")
	public ModelAndView remove(@RequestParam long id,ModelAndView mav) {
		recipeService.deleteById(id);
		return new ModelAndView("redirect:/");
	}
}

検索(リダイレクトなし)

フロー

[ユーザーのブラウザ]
        │
        ▼
  index.html(検索フォーム)
  ┌──────────────────────────────┐
  │ <form method="get" action="/search"> │
  │   <input name="dish-name">           │
  │   <button>検索</button>              │
  └──────────────────────────────┘
  
  ★★ クエリパラメータのしくみ ★★ 
  inputタグのname属性=クエリパラメータのキー名とすることで
  inputタグに入れられた検索キーワード=クエリパラメータの値とすることができる
  
        │
        ▼
 GETリクエスト(/search?dish-name=〇〇)
        │
        ▼
[Spring Bootのコントローラー]
  SearchController#search()
  ┌─────────────────────────────────────────────┐
  │ @RequestParam("dish-name") → 値はkeywordに格納   
  │ mod.addAttribute("hoge", keyword)  hogeに値が渡る│
  │ return "index";  → index.htmlに戻る          │
  └─────────────────────────────────────────────┘
        │
        ▼
 Thymeleafテンプレートエンジンが index.html を処理
 ┌───────────────────────────────────────────────┐
 │ <p th:text="${hoge} + 'に関する検索結果を表示します'> │
 │   → 例:「カレーに関する検索結果を表示します」       │
 └───────────────────────────────────────────────┘
        │
        ▼
[ユーザーのブラウザに表示]
「カレーに関する検索結果を表示します」

Model

  • Modelとはコントローラとビューの間でデータをやり取りする役割を持つオブジェクト
  • コントローラで生成したデータをビューに渡せる
  • addAttributeメソッドでViewで使う変数とその変数に格納したいデータを定義

@RequestParam(クエリパラメータ)

  • クエリパラメータの役割を提供
  • ブラウザからサーバーに検索キーワード、ページ番号、並び順などの追加条件やオプションを渡すときに使用

コントローラーからの変数の受け取り

タイムリーフ構文のなかで${変数}とする

コード

index.html
<body>
 	<p>レシピを検索してください</p>
 	<form method="get" action="/search">
 		<input type="text" name="dish-name" placeholder="料理名をどうぞ">
 		<button type="submit">検索</button>
 	</form>
 	
 	<p th:text="${hoge}+'に関する検索結果を表示します'"></p>
 </body>
SearchControeller.java
package com.example.demo;
 
 
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; //追記
import org.springframework.web.bind.annotation.RequestParam; ///追記
 
 
 @Controller
 public class SearchController {
 	@RequestMapping(value="/search",method=RequestMethod.GET)
 	//クエリパラメータ dish-name を keyword 変数に格納
 	public String search(@RequestParam("dish-name") String keyword,Model mod) {
 		mod.addAttribute("hoge",keyword);
 		return "index";
 	}
 }

Create

フロー

[ユーザーのブラウザ]
        │
        ▼
 GET /create リクエスト
        │
        ▼
[NewController#create()]
  ┌─────────────────────────────────────────────┐
  │ ModelAndView:                               │
  │   - View指定: "create"                      │
  │   - 変数title: "新しいレシピを作ってください!"│
  │   - formModel: new Recipe()                 │
  └─────────────────────────────────────────────┘
  空のformModel変数にRecipeクラスのインスタンスを作成&格納してビューに渡す
        │
        ▼
[create.html](Thymeleafで表示)
  ┌──────────────────────────────────────┐
  │ <form th:object="${formModel}">      │
  │   <input th:field="*{name}">         │ ← Recipe.name
  │   <input th:field="*{comment}">      │ ← Recipe.comment
  └──────────────────────────────────────┘
        │
        ▼
[ユーザーがフォームに入力](Recipeのプロパティを入力してもらう)
  例:name=カレー, comment=簡単に作れるよ
        │
        ▼
 POST /create リクエスト(form送信)
 FormModel.name=カレー,FormModel.comment=簡単に作れるよなどをformModel(Recipeオブジェクト)に格納してコントローラーに送信
        │
        ▼
[NewController#post()]
@ModelAttributeでFormModel→Recipeオブジェクトに変換して
recipe.name,recipe.commentにする
  ┌─────────────────────────────────────────────┐
  │ @ModelAttribute("formModel") Recipe recipe   │
  │ repository.saveAndFlush(recipe);             │ ← DB保存
  │ return redirect:/                            │ ← トップへリダイレクト
  └─────────────────────────────────────────────┘
        │
        ▼
[データベース]
  ┌────────────────────────────┐
  │ Recipeエンティティに保存     │
  │ id, name="カレー", comment=...│
  └────────────────────────────┘
        │
        ▼
[トップページへリダイレクト]

ModelAttribute(フォームをオブジェクトに)

  • リクエストパラメータ(フォームの値)をオブジェクトに変換し、Controllerの引数として受け取れるアノテーション
  • HTMLフォームを th:object="formModel"とすることで、そのフォームは formModel という名前のオブジェクトとつながる
    →formModel.name, formModel.comment としてコントローラーにデータが送信される
    →送信されたデータをRecipe型変数recipe の name, comment プロパティにマッピングしたいので@ModelAttribute("フォームのオブジェクト名") 型 受け取る変数 とすることで
    formModel.name(=カレー), formModel.comment(=簡単に作れるよ)が
    recipe.name(=カレー), recipe.comment(=簡単に作れるよ)になる
  • あとはリポジトリを通じて(=リポジトリのメソッドで)それを保存すればDB保存ができる

コード

リポジトリ

CookingRepository.java
package com.example.demo.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.example.demo.Recipe;

public interface CookingRepository extends JpaRepository<Recipe,Long>{
//リポジトリと接続したいエンティティクラスとその主キーの型を指定
}

ビュー

create.html
<!DOCTYPE html>
 
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title th:text="${title}"></title>
</head>
<body>
	<form name="new" action="/create" method="post" th:object="${formModel}">
		<label for="dish-name">料理名</label>
		<input type="text" name="name" id="dish-name" th:field="*{name}">
		
		
		<label for="dish-comment">コメント</label>
		<input type="text" name="comment" id="dish-comment" th:field="*{comment}">
		<input type="submit"  class="btn btn-primary"value="作成"/>
	</form>
</body>
</html>
  • inputタグのid属性とlabelのfor属性は基本的に一致させる
    →ラベルをクリックしたときに対応する入力欄にフォーカスが当たるようになる
  • nullable = false=「データベースでNULL禁止」似ているけどrequired = trueは画面(HTMLフォームなど)レベルで「この入力フィールドは必須」というバリデーションを意味する

コントローラー

NewController.java
package com.example.demo;
import jakarta.transaction.Transactional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import com.example.demo.repository.CookingRepository;

@Controller


public class NewController {
	@Autowired
	CookingRepository repository;
//Createページを表示
	@RequestMapping(value="/create")
	public ModelAndView create(ModelAndView mav) {
		mav.setViewName("create");
		mav.addObject("title","新しいレシピを作ってください!");
		mav.addObject("formModel", new Recipe()); // ← これを追加
		return mav;
	}
//サーバーに新しいデータを送信
	
	@Transactional
	@PostMapping("/create")
	public ModelAndView post(@ModelAttribute("formModel") Recipe recipe, ModelAndView mav) {
		mav.setViewName("create");
		repository.saveAndFlush(recipe);
		return new ModelAndView("redirect:/");
	}
}

スクリーンショット (171).png
スクリーンショット (172).png

Update

フロー

【① 編集画面の表示(GET /edit/{id})】

         [ ユーザーがURLにアクセス ]
                    |
                /edit/5
                    ↓
       ┌────────────────────────────┐
       │ UpdateController.edit(...) │ ← @PathVariable で id=5 を受け取る
       └────────────────────────────┘
                    ↓
      repository.findById(5) でデータ取得
                    ↓
      Optional<Recipe> data に格納
         ↓            ↓
 [あれば] data.get() で取り出す
 [なければ] null + メッセージ表示
                    ↓
     formModel として View に渡す
                    ↓
          ┌────────────────┐
          │   edit.html    │
          └────────────────┘
                    ↓
      th:object="${formModel}" を使い
      th:field="*{name}", *{comment} で
      料理名・コメントを初期表示!

---------------------------------------------------

【② 編集内容を送信して更新(POST /edit)】

         [ ユーザーが編集して「更新」押下 ]
                    ↓
       フォームが POST /edit を送信
         name, comment, id を含む
                    ↓
     ┌────────────────────────────┐
     │ UpdateController.update(...)│
     └────────────────────────────┘
                    ↓
@ModelAttribute Recipe recipe に
フォームの内容がバインドされる
  recipe.id = 5, name = ..., comment = ...
                    ↓
repository.saveAndFlush(recipe) により
  → 既存IDのレコードが更新される!
                    ↓
     redirect:/ にリダイレクト(トップへ)

PathVariable

  • ユーザーIDや記事IDなど特定のリソースへのアクセス時に使用
  • URLの一部(=パスパラメータ)をメソッド引数にバインドするためのアノテーション
  • リクエストマッピングのルート指定の部分で{}部分をパスパラメータとして取得→@PathVariableのついた変数に格納
ユーザーがアクセス:     /edit/5
                            ↓
URLマッピング:         /edit/{id}
                            ↓
@PathVariable で受け取る:  int id = 5

ModelAndView

  • データと画面の指定を同時に行えるクラス
  • データと画面を指定して最後にreturnすれば指定したテンプレートに指定したデータが渡る

コード

コントローラー

UpdateController.java
package com.example.demo;
import java.util.List;
import java.util.Optional;

import jakarta.transaction.Transactional;
import jakarta.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.bind.annotation.PostMapping;

import com.example.demo.Recipe;
import com.example.demo.repository.CookingRepository;

@Controller
public class UpdateController {
@Autowired
CookingRepository repository;
@RequestMapping("/edit/{id}")//パスパラメータ
public ModelAndView edit(ModelAndView mav,@PathVariable int id) {
	mav.setViewName("edit");
	Optional<Recipe> data = repository.findById((long)id);
	if(data.isPresent()) {
		mav.addObject("formModel",data.get());	
	}else if(data.isEmpty()){
		mav.addObject("formModel",null);
		mav.addObject("message","データが見つかりません");
	}
	
	return mav;
}
//保存
@PostMapping("/edit")
@Transactional
public ModelAndView update(@ModelAttribute Recipe recipe,ModelAndView mav) {
	repository.saveAndFlush(recipe);
	return new ModelAndView("redirect:/");
}
}
  • Optional は「値があるかもしれないし、ないかもしれない」ということを表すJavaのクラス
    →レコードが見つかれば Optional に中身が入っていて、見つからなければ中身が「空(empty)」
  • data は Optional 型
    →data.get() をすると、中にある Recipe インスタンスを取り出すことができる

ビュー

edit.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>レシピの変更</title>
</head>
<body>
	<div th:if="${formModel==null}">
		<p th:text="${message}"></p>
	</div>
			<div th:if="${formModel != null}">
				<form name="update" action="/edit" method="post" th:object="${formModel}">
						<input type="hidden" name="id" th:field="*{id}"/>
						<label for="dish-name">料理名</label>
						<input type="text" name="name" id="dish-name" th:field="*{name}">
						
						
						<label for="dish-comment">コメント</label>
						<input type="text" name="comment" id="dish-comment" th:field="*{comment}">
						<input type="submit"  class="btn btn-primary"value="更新"/>
				</form>
			</div>
	
</body>
</html>

Delete

フロー

【① 削除画面の表示(GET /delete/{id})】

         [ ユーザーがURLにアクセス ]
                    |
                /delete/3
                    ↓
       ┌────────────────────────────┐
       │ DeleteController.delete(...) │ ← @PathVariable で id=3 を受け取る
       └────────────────────────────┘
                    ↓
        recipeService.findById(3) でデータ取得
                    ↓
        Optional<Recipe> data に格納
           ↓            ↓
     [あれば] data.get() で取り出す
     [なければ] null + エラーメッセージ
                    ↓
        formModel として View に渡す
                    ↓
          ┌────────────────┐
          │   delete.html   │
          └────────────────┘
                    ↓
     th:object="${formModel}" によりフォーム構成
     th:text="*{name}", *{comment} でレシピ情報を表示!


【② 削除実行(POST /delete_clicked)】

        [ ユーザーが「削除」ボタン押下 ]
                    ↓
     フォームが POST /delete_clicked を送信
         (hidden の id を含む)
                    ↓
     ┌────────────────────────────┐
     │ DeleteController.remove(...) │ ← @RequestParam long id で受け取る
     └────────────────────────────┘
                    ↓
        recipeService.deleteById(id)
           → IDに対応するレコードを削除!
                    ↓
        new ModelAndView("redirect:/")
           → トップページへリダイレクト!

コード

DeleteController.java
package com.example.demo;
import java.util.List;


import java.util.Optional;

import jakarta.transaction.Transactional;
import jakarta.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import com.example.demo.Recipe;
import com.example.demo.repository.CookingRepository;
import org.springframework.web.bind.annotation.RequestParam;
import com.example.demo.Services.RecipeService;

@Controller
public class DeleteController {
	
	private final RecipeService recipeService;
	public DeleteController(RecipeService recipeService) {
		this.recipeService = recipeService;
	}
	
	//特定のIDのリソースを画面表示
	@RequestMapping(value="/delete/{id}",method= RequestMethod.GET)
	public ModelAndView delete(@PathVariable int id,ModelAndView mav) {
		mav.setViewName("delete");
		mav.addObject("msg","どのレシピを削除しますか?");
		Optional<Recipe> data = recipeService.findById((long)id);
		if(data.isPresent()) {
			mav.addObject("formModel",data.get());
		}else if(data.isEmpty()){
			mav.addObject("formModel",null);
			mav.addObject("message","データがみつかりません");
		}
		return mav;
	}
	
	@PostMapping("/delete_clicked")
	
	public ModelAndView remove(@RequestParam long id,ModelAndView mav) {
		recipeService.deleteById(id);
		return new ModelAndView("redirect:/");
	}
}
delete.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>削除画面</title>
</head>
<body>
<p th:text="${msg}"></p>
<div th:if="${formModel == null}">
	<p th:text="${message}"></p>
</div>
<div th:if="${formModel != null}">
<form name="eliminate" method="post" action="/delete_clicked" th:object="${formModel}">
	<input type="hidden" name="id" th:field="*{id}"/>
	<ul>
	      <li>料理名:  <span th:text="*{name}"></span></li>
	      <li>コメント: <span th:text="*{comment}"></span></li>

	 </ul>
	<input type="submit"  class="btn btn-primary"value="削除"/>
</form>
</div>
</body>
</html>

🔄 補足:もし @ModelAttribute を使ったら?
フォームで複数フィールド(nameやcomment)を送信する場合は、こういう形にもできる:

ex.java
@PostMapping("/delete_clicked")
public String remove(@ModelAttribute Recipe formModel) {
    repository.deleteById(formModel.getId());
    return "redirect:/";
}

フォームの各フィールド(idやnameなど)の値をRecipeクラスの該当するフィールドに自動でセットして、できあがったRecipeオブジェクトをformModelという変数名で使えるようにしている
(POST送信されたらようやく一人前のRecipeオブジェクトに進化して、formModelという変数に格納して使えるようになったイメージ)

変数の値がnullの場合はテキストを非表示にする

  • JSによる解決はどうか?
    →th:text="${hoge}+'に関する検索結果を表示します'" はサーバーサイド(Thymeleaf)で処理されるため、JavaScriptで操作する前にHTMLとして出力されてしまう
    →JavaScriptで非表示にするには、HTMLを一度出力してから非表示にすることになるため、不自然(SEO的にもよくない)
  • Thymeleafでは ${hoge} が null や空文字かどうかを条件分岐できる
    →th:if 属性を使えば、条件を満たさないときは そのタグ自体が出力されない
ex.html
<p th:if="${hoge ! = null and !#strings.isEmpty(hoge)}"
	th:text="${hoge}+'に関する検索結果を表示します'"></p>
  • ${...}:Thymeleafの式評価。Model に渡された値をここで参照
  • hoge ! = null→モデル変数 hoge が null でないことを確認
  • #strings は Thymeleaf のビルトインオブジェクト「文字列ユーティリティ」
  • isEmpty(...) は Javaの String.isEmpty() と同様で、文字列が ""(長さ0)かどうかを判定
  • !#strings.isEmpty(hoge) は 「hogeが空文字じゃない」という意味
  • 「かつ」は&&ではなくandなので書き間違い注意

コントローラー側でnullチェックして、必要なときだけ値を渡す方法もある

ex.html
<!-- 例: JSPの場合 -->
<c:if test="${showResult}">
  <p>${hoge}に関する検索結果を表示します</p>
</c:if>
Controller.java
@RequestMapping("/search")
public String search(@RequestParam(value = "dish-name", required = false) String keyword, Model model) {
    boolean showResult = keyword != null && !keyword.trim().isEmpty();
    model.addAttribute("showResult", showResult);
    model.addAttribute("hoge", keyword);
    return "index";
}
  • required = false→このパラメータが無くてもエラーにしない という意味
  • デフォルトでは @RequestParam は必須(required = true)なので、URLに ?dish-name= が無いと Spring はエラーを出す
    例えばhttp://localhost:8080/searchというアクセスしたらdish-name が指定されていないため、required = false をつけておかないとエラーになる
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?