【食事内容管理Webアプリ】データ登録処理の追加

前回までのあらすじ&今回の目標

前回は、FullCalendarの「Toolbar」を用いた登録ボタンの追加とデータ登録用のモーダル画面を作成しました。

今回はモーダル画面からデータが登録ができることを目標に進めていきます。

既存テーブルの修正 (カラム追加)

最初に既存テーブルの修正を行っていきます。

モーダル画面にメモ欄を作成したので、その情報を保持できるようにします。今回は既存テーブルである「menu」にカラムを追加することにしました。ER図は下記の通りです。

○ER図
ER図

DDL (memoカラム追加)
alter table menu add memo VARCHAR(150) default null after dish_code 

実装

POST時の処理の大まかな流れは以下の通りです。

  1. Controllerクラスでformタグの内容をFormクラスで受け取る
  2. 受け取った内容をServiceクラスに渡す
  3. ServiceクラスからRepositoryクラスを利用してデータベースに登録を行う

○簡易図
post時の簡易図

上記を基に順に実装を行います。

htmlの修正

まず、登録内容をサーバ側に渡すためのformタグを修正します。今回は大きく分けて三つの修正を行います。

一つ目は食事の分類を選択するラジオボタン部分の修正です。前回まではラベル名と値を固定値としていましたが、「meal_typesテーブル」の内容を設定できるように修正します。

またPOST時に選択したデータがFormクラスのオブジェクトに設定されるように、属性にth:fieldを追加します。

registration.html (ラジオボタン部分抜粋 修正前)
<div class="modal-content-input-area">
  <input id="meal-type-1" type="radio" name="meal-types">
  <label for="meal-type-1">朝食</label>
  <input id="meal-type-2" type="radio" name="meal-types">
  <label for="meal-type-2">昼食</label>
  <input id="meal-type-3" type="radio" name="meal-types">
  <label for="meal-type-3">夕食</label>
</div>
registration.html (ラジオボタン部分抜粋 修正後)
<div class="modal-content-input-area" th:each="mealType : ${mealTypes}">
	<input th:id="meal-type- + ${mealType.mealTypeCode}" type="radio" name="meal-types" th:value="${mealType.mealTypeCode}" th:field="*{mealTypeCode}">
	<label th:for="meal-type- + ${mealType.mealTypeCode}" th:text="${mealType.mealTypeName}"></label>
</div>

二つ目はラジオボタン以外のinputタグの修正です。こちらも同様に属性にth:fieldを追加します。

そして三つ目の修正はformタグの修正です。こちらの属性にth:objectmethodを追加します。

上記修正を全て反映したformタグは下記の通りです。

registration.html (formタグ抜粋)
<form th:action="@{registration}" th:object="${menuRegistrationForm}" method="post">
	<div class="modal-content-row">
		<span class="material-symbols-outlined material-symbols-big-font">edit_calendar</span>
		<input id="eating-date" type="date" th:field="*{eatingDate}">
	</div>
	<div class="modal-content-row">
		<span class="material-symbols-outlined material-symbols-big-font">chat_info</span>
		<div class="modal-content-input-area" th:each="mealType : ${mealTypes}">
			<input th:id="meal-type- + ${mealType.mealTypeCode}" type="radio" name="meal-types" th:value="${mealType.mealTypeCode}" th:field="*{mealTypeCode}">
			<label th:for="meal-type- + ${mealType.mealTypeCode}" th:text="${mealType.mealTypeName}"></label>
		</div>
	</div>
	<div class="modal-content-row">
		<span class="material-symbols-outlined material-symbols-big-font">restaurant</span>
		<div class="modal-content-input-area">
			<input type="text" th:field="*{dishName}" placeholder="料理名を入力">
		</div>
	</div>
	<div class="modal-content-row">
		<span class="material-symbols-outlined material-symbols-big-font">description</span>
		<input type="text" th:field="*{memo}" placeholder="メモを入力">
	</div>
	<div class="submit">
		<input type="submit" value="上記内容で登録" class="submit-button">
	</div>
</form>

Formクラスの追加

次にformタグの内容をControllerクラスで受け取るために必要なFormクラスを追加します。

formタグにはeatingDatemealTypeCodedishNamememoのフィールドを設定したためFormクラスにも四つのフィールドを用意します。

MenuRegistrationForm.java
@Data
public class MenuRegistrationForm {

	/** 実食日 */
	private String eatingDate;
	/** 食事分類コード */
	private String mealTypeCode;
	/** 料理名 */
	private String dishName;
	/** メモ */
	private String memo;
	
	@Override
	public String toString() {
		
		String str = MessageFormat.format("""
				eatingDate:{0}, 
				mealTypeCode:{1}, 
				dishName:{2}, 
				memo:{3}"""
				, eatingDate, mealTypeCode, dishName, memo);
		
		return str;
	}
}

Entityクラスの作成

本Webアプリで使用するテーブル用のEntityクラスを作成します。作成するクラスは各テーブルの項目をフィールドに持ち、フィールドに値を設定しながら自身の参照を返す関数を持たせます。

Menu.java
@Data
public class Menu {

	/* 献立表ID */
	private long menuId;
	/* 実食日 */
	private String eatingDate;
	/* 食事分類コード */
	private String mealTypeCode;
	/* 料理コード */
	private String dishCode;
	/* メモ */
	private String memo;
	
	public Menu menuId(long menuId) {
		this.menuId = menuId;
		return this;
	}
	
	public Menu eatingDate(String eatingDate) {
		this.eatingDate = eatingDate;
		return this;
	}
	
	public Menu mealTypeCode(String mealTypeCode) {
		this.mealTypeCode = mealTypeCode;
		return this;
	}
	
	public Menu dishCode(String dishCode) {
		this.dishCode = dishCode;
		return this;
	}
	
	public Menu memo(String memo) {
		this.memo = memo;
		return this;
	}
}
Dishes.java
@Data
public class Dishes {

	// 料理コード
	private String dishCode;
	// 料理名
	private String dishName;
	
	public Dishes dishCode(String dishCode) {
		this.dishCode = dishCode;
		return this;
	}
	
	public Dishes dishName(String dishName) {
		this.dishName = dishName;
		return this;
	}
}
MealTypes.java
@Data
public class MealTypes {
	
	/** 食事分類コード */
	private String mealTypeCode;
	/** 食事分類名 */
	private String mealTypeName;
	/** カラー */
	private String color;
	
	public MealTypes mealTypeCode(String mealTypeCode) {
		this.mealTypeCode = mealTypeCode;
		return this;
	}
	
	public MealTypes mealTypeName(String mealTypeName) {
		this.mealTypeName = mealTypeName;
		return this;
	}
	
	public MealTypes color(String color) {
		this.color = color;
		return this;
	}

}

Controllerクラスの修正

まず、既存のshowCalendarについてはregistration.htmlの変更に伴い、二つの修正を行います。

一つ目は「meal_typesテーブル」からのデータ取得です。こちらはServiceクラスの処理を呼び出してその戻り値をmodelに格納します。

二つ目はmodelにFormクラスを格納する修正を行っています。これはregistration.htmlのth:objectに指定しているFormクラスを用意するためですね。


次にpostRegistrationを追加します。引数には@ModelAttributeを付与したFormクラスを指定しており、これによってこの処理が呼び出されるとformタグの内容がFormクラスに自動で紐づけられるようになります。

処理の最後にはformタグの内容をEntityクラスのオブジェクトであるmenuおよびdishに設定し、Serviceクラスに渡してデータベースへの登録を依頼します。

また登録完了後はリダイレクトで初期画面に戻ります。

FoodAppController.java
@Controller
@RequestMapping("/foodapp")
public class FoodAppController {
	
	@Autowired
	CalendarManagementService calendarManagementService;
	
	@GetMapping("calendar")
	public String showCalendar(HttpSession session, Model model) {
		
		Date date = new Date(session.getCreationTime());
		String year = new SimpleDateFormat("yyyy").format(date);
		String month = new SimpleDateFormat("MM").format(date);
		
		List<EventBean> events = calendarManagementService.getEventsOfMonth(year, month);
		List<MealTypes> mealTypes = calendarManagementService.getMealTypes();
		model.addAttribute("events", events);
		model.addAttribute("mealTypes", mealTypes);
		model.addAttribute("menuRegistrationForm", new MenuRegistrationForm());
		
		return "calendar";
	}
	
	@PostMapping("registration")
	public String postRegistration(Model model, @ModelAttribute MenuRegistrationForm form) {
				
		Menu menu = new Menu()
				.eatingDate(form.getEatingDate())
				.mealTypeCode(form.getMealTypeCode())
				.memo(form.getMemo());
		
		Dishes dish = new Dishes()
				.dishName(form.getDishName());
		
		calendarManagementService.registerEvent(menu, dish);
		
		return "redirect:calendar";
	}
	
}

Serviceクラスの修正

「meal_typesテーブル」のデータ取得用の処理と「menuテーブル」、「dishesテーブル」へのデータ登録用の処理を追加します。

処理内容についてはRepositoryクラスで実装することになりますね。

CalendarManagementServiceImpl.java
@Service
class CalendarManagementServiceImpl implements CalendarManagementService {

	@Autowired
	CalendarRepository calendarRepository;
	
	@Override
	public List<EventBean> getEventsOfMonth(String year, String month) {
		return calendarRepository.getEvents(year, month);
	}
	
	@Override
	public List<MealTypes> getMealTypes() {
		return calendarRepository.getMealTypes();
	}

	@Override
	public void registerEvent(Menu menu, Dishes dish) {
		calendarRepository.registerEvent(menu, dish);
	} 
	
}

Repositoryクラスの修正

RepositoryクラスではServiceクラスから呼ばれる処理の実装を追加します。

データ登録用の処理については、登録前に「dishesテーブル」に料理名が登録済みでないか確認して料理名の重複を避ける設計とします。

実際の修正内容は下記の通りで、今回の修正で追加した処理のみを記載しています。

CalendarRepositoryImpl.java
@Repository
public class CalendarRepositoryImpl implements CalendarRepository {
	
	...
	
	@Override
	public List<MealTypes> getMealTypes() {
		List<MealTypes> mealTypes = jdbcTemplate.query(
				SqlConst.getQueryFromXml(SqlConst.MEAL_TYPES_SELECT_ALL),
				(rs, rowNum) -> new MealTypes()
										.mealTypeCode(rs.getString("mealTypeCode"))
										.mealTypeName(rs.getString("mealTypeName"))
										.color(rs.getString("color")));
		
		return mealTypes;
	}

	@Override
	@Transactional
	public void registerEvent(Menu menu, Dishes dish) {
				
		List<Dishes> dishes = jdbcTemplate.query(
			SqlConst.getQueryFromXml(SqlConst.DISHES_EXTRACT_DISH_CODE), 
			(rs, rowNum) -> new Dishes().dishCode(rs.getString("dishCode")),
			dish.getDishName());
		
		if (dishes.isEmpty()) {
		  // 料理名を新規登録
      jdbcTemplate.update(
      	SqlConst.getQueryFromXml(SqlConst.DISHES_REGISTER),
      	new Object(){}.getClass().getEnclosingMethod().getName(),
      	dish.getDishName());
      
      // 登録内容を取得
      dishes = jdbcTemplate.query(
      	SqlConst.getQueryFromXml(SqlConst.DISHES_EXTRACT_DISH_CODE), 
      	(rs, rowNum) -> new Dishes().dishCode(rs.getString("dishCode")),
      	dish.getDishName());
		}
		
		jdbcTemplate.update(
			SqlConst.getQueryFromXml(SqlConst.MENU_REGISTER), 
			menu.getEatingDate(),
			menu.getMealTypeCode(),
			dishes.get(0).getDishCode(),
			menu.getMemo());
	}

}

SQL関係の修正

最後にRepositoryクラスでデータベースの操作ができるようにSqlConstクラスと各テーブルのxmlファイルを修正・新規作成します。

SqlConstクラスにはsql取得用の固定文字列を追加します。

SqlConst.java
public class SqlConst {
	
	...
	
	/* menu用クエリ */
	public static final String MENU_FOR_EACH_MONTH = "menu.forEachMonth";
	public static final String MENU_REGISTER = "menu.register";
	/* dishes用クエリ */
	public static final String DISHES_EXTRACT_DISH_CODE = "dishes.extractDishCode";
	public static final String DISHES_REGISTER = "dishes.register";
	/* meal_types用クエリ */
	public static final String MEAL_TYPES_SELECT_ALL = "mealTypes.selectAll";
	
	...
}

各テーブルのxmlファイルは下記の通りに修正します。

menu.xml (取得項目memoの追加、registerの追加)
<?xml version="1.0" encoding="UTF-8"?>
<sql table="menu">
	<query id="forEachMonth" comment="各月のメニューを取得">
		<![CDATA[
			select
				menu.eating_date start
				, dishes.dish_name title
				, menu.meal_type_code code
				, meal_types.color backgroundColor
				, menu.memo
			from
				menu
			inner join
				dishes
			on
				menu.dish_code = dishes.dish_code
			inner join
				meal_types
			on
				menu.meal_type_code = meal_types.meal_type_code
			where
				menu.eating_date between str_to_date(?, '%Y%m%d') and last_day(str_to_date(?, '%Y%m%d'))
			order by
				start
				, code
		]]>
	</query>
	<query id="register" comment="メニューを登録する">
		<![CDATA[
			insert into
				menu
			(
				eating_date
				, meal_type_code
				, dish_code
				, memo
			)
			values (
				?
				, ?
				, ?
				, ?
			)
		]]>
	</query>
</sql>
dishes.xml (新規作成)
<?xml version="1.0" encoding="UTF-8"?>
<sql table="dishes">
	<query id="register" comment="料理を登録する">
		<![CDATA[
			insert into
				dishes
			(
			    created_by
				, dish_code
				, dish_name
			)
			with dish as (
			    select
			        lpad(max(dish_code) + 1, 7, '0') as next_dish_code
			    from
			        dishes
			)
			select
				?
			    , next_dish_code
			    , ?
			from
			    dish
		]]>
	</query>
	<query id="extractDishCode" comment="料理名が一致するデータの料理コードを取得する">
		<![CDATA[
			select
				dish_code dishCode
			from
				dishes
			where
				dish_name = ?
		]]>
	</query>
</sql>
mealTypes.xml (新規作成)
<?xml version="1.0" encoding="UTF-8"?>
<sql table="mealTypes">
	<query id="selectAll" comment="全件取得する">
		<![CDATA[
			select
				meal_type_code mealTypeCode
				, meal_type_name mealTypeName
				, color color
			from
				meal_types
			where
				delete_flag = '0'
		]]>
	</query>
	<query id="extractDishCode" comment="料理名が一致するデータの料理コードを取得する">
		<![CDATA[
			select
				dish_code dishCode
			from
				dishes
			where
				dish_name = ?
		]]>
	</query>
</sql>

動作確認

では実際に登録操作をしてみます。

○登録前
登録前

○登録内容
登録内容

○登録後
登録後

無事に登録処理を行って画面に表示することができました!

今後の予定

今回はhtmlのformタグとサーバ側のJavaおよびテーブルを修正してデータ追加処理を実装しました。

ただし今のままだと入力したメモの内容が画面から確認できません。

そのため次回はFullCalendarのイベントにメモの内容を持たせて画面上で確認できるようにしていきたいと思います。

今夜はエビチリでした。それではまた。。

コメント

タイトルとURLをコピーしました