【食事内容管理Webアプリ】データベースから値を取得する

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

カレンダー作成②では、表示させるメニューを「JavaScriptに直書きしたデータ」から「サーバ上で用意したデータ」に置き換えることを行いました。

そして今回はデータベースに登録されているデータに置き換えていきます!

なお、データベースは「MySQL」を使用しますが、インストール等の事前準備は今回省いています。

開発環境
・Java:17.0.12
・Spring Boot:3.4.1
・MySQL:8.0.37

データベースの準備

用意するテーブルは以下のようにしました。

テーブル名内容
menuメニューを登録するテーブル
dishes料理名をもつテーブル
meal_types食事の分類とそれに対応する色をもつテーブル

ER図は下記の通りです。menuテーブルに日々のメニューを登録していき、料理名や食事の分類(朝食、昼食、夕食など)は別のテーブルで管理して、取得時に結合するようにしています。

○ER図

DDL
create table dishes (
  dish_code CHAR(7) not null
  , dish_name VARCHAR(90) not null
  , primary key (dish_code)
)

create table meal_types (
  meal_type_code CHAR(1) not null
  , meal_type_name VARCHAR(30) not null
  , color VARCHAR(12) not null
  , primary key (meal_type_code)
)

create table menu (
  menu_id BIGINT not null auto_increment
  , eating_date DATE not null
  , meal_type_code CHAR(1) not null
  , dish_code CHAR(7) not null
  , primary key (menu_id)
  , constraint fk_dish_code foreign key (dish_code) references dishes(dish_code)
  , constraint fk_meal_type_code foreign key (meal_type_code) references meal_types(meal_type_code)
)

ソース等の修正

build.gradleの修正

次にMySQLのデータにアクセスするのに必要なライブラリを追加するため、build.gradleの修正を行います。

以下のように「spring-boot-starter-data-jdbc」「mysql-connector-j」を追加しました。

build.gradle
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jdbc' // 追加
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	runtimeOnly 'com.mysql:mysql-connector-j' // 追加
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

新規プロジェクト作成時はbuild.gradleの修正は不要で、「新規Springスターター・プロジェクト依存関係」の画面上で「Spring Data JDBC」「MySQL Driver」を選択するだけで設定が可能です。

application.propertiesの修正

build.gradleの修正が完了したらapplication.properrtiesの修正を行います。ここではデータベースにアクセスする際の各情報を記載しました。

application.properties
# データベースのURL(db_foodappスキーマに接続する)
spring.datasource.url=jdbc:mysql://localhost:3306/db_foodapp
# ユーザ名
spring.datasource.username=root
# パスワード
spring.datasource.password=*******
# ドライバ
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 文字コード
spring.sql.init.encoding=utf-8

データ取得部分の修正

上記が完了したら、いよいよデータ取得部分の修正を行っていきます。

まずControllerクラスですが、「/foodapp/calendar」にアクセスされた場合に動作する関数を下記のように変更しました。

変更箇所は複数ありますが、データ取得処理をServiceクラスで行っているところ(15行目)が一番大きな変更点になります。

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);
		model.addAttribute("events", events);
		
		return "calendar";
	}
	
}

そのServiceクラスの実装クラスは、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);
	}
	
}

Repositoryクラスの実装クラスでは、渡された「年・月」の文字列から対象月のデータを複数取得し、それぞれをEventBeanインスタンスに格納して戻す処理を実装しています。SqlConstクラスはxmlファイルに記載したSQLの文字列を戻すクラスとなっています。

CalendarRepositoryImpl.java
@Repository
public class CalendarRepositoryImpl implements CalendarRepository {
	
	@Autowired
	JdbcTemplate jdbcTemplate;

	@Override
	public List<EventBean> getEvents(String year, String month) {
		String date = year + month + "01";
		
		List<EventBean> events = jdbcTemplate.query(
				SqlConst.getQueryFromXml(SqlConst.MENU_FOR_EACH_MONTH),
				(rs, rowNum) -> new EventBean()
										.start(rs.getString("start"))
										.title(rs.getString("title"))
										.backgroundColor(rs.getString("backgroundColor")),
				date,
				date);
		
		return events;
	}

}
SqlConst.java
public class SqlConst {
	
	private static final String XML_FOLDER_PATH = System.getProperty("user.dir")
			+ "\\src\\main\\resources\\sql\\";
	
	public static final String MENU_FOR_EACH_MONTH = "menu.forEachMonth";
	
	public static String getQueryFromXml(String queryId) {

		String sql = null;
		
		// 引数からテーブル名とIDを取得
		String tableName = queryId.split("\\.")[0];
		String id = queryId.split("\\.")[1];
		
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		
		try {
			DocumentBuilder builder = factory.newDocumentBuilder();
			
			// 引数よりxmlファイルを特定
			Document doc = builder.parse(new File(XML_FOLDER_PATH + tableName + ".xml"));

			NodeList sqlList = doc.getElementsByTagName("query");

			// idが同じノードの値を取得する
			for (int i = 0; i < sqlList.getLength(); i++) {
				Node node = sqlList.item(i);
				Node attr = node.getAttributes().getNamedItem("id");

				if (attr.getNodeValue().equals(id)) {
					sql = node.getTextContent();
					break;
				}
			}

		} catch (ParserConfigurationException | SAXException | IOException e) {
			// 今回、例外処理は割愛
			e.printStackTrace();
		}

		return Objects.nonNull(sql) ? sql.replaceAll("^\t", "") : null;
	}
}
menu.xml
<?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
			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>
</sql>

最後にBeanクラスですが、こちらはRepositoryクラスのラムダ式で記述が簡略ができるように引数をフィールドにセットした後に自身の参照値を返す関数を追加しました。

またカレンダーに表示した際にメニューの境界線の色が見にくかったため、「borderColor」のプロパティ(10行目)も追記しています。

EventBean.java
@Data
public class EventBean {
  // 食べた日付
	private String start;
	// 食べた料理名
	private String title;
	// 朝食:red、昼食:orange、夕食:blue
	private String backgroundColor;
	// 画面に表示する際の境界線の色
	private String borderColor = "white";
	// startフィールドを更新して自身の参照値を返す
	public EventBean start(String start) {
		this.start = start;
		return this;
	}
	// titleフィールドを更新して自身の参照値を返す
	public EventBean title(String title) {
		this.title = title;
		return this;
	}
	// backgroundColorフィールドを更新して自身の参照値を返す
	public EventBean backgroundColor(String backgroundColor) {
		this.backgroundColor = backgroundColor;
		return this;
	}
}

動作確認

各テーブルには下記の通りデータを登録して、いざアクセスするとしっかりデータベースの内容が反映されていることが確認できました!

menu_ideating_datemeal_type_codedish_code
12025/02/0110000001
22025/02/0120000002
32025/02/0130000003
42025/02/0230000004
52025/02/0310000005
menuテーブル
dish_codedish_name
0000001パン
0000002ハンバーグ
0000003唐揚げ丼
0000004恵方巻
0000005おにぎり
dishesテーブル
meal_type_codemeal_type_namecolor
1朝食red
2昼食orange
3夕食blue
meal_typesテーブル

○出力結果
出力結果

これで今後はテーブルにデータを登録していくだけで画面に自動反映されるようになりました!

今後の予定

今回はデータベースに登録されているデータを取得して画面に反映させるところまで進めました。だんだんWebアプリっぽくなってきましたね!

次回はデータベースにデータを登録する画面の実装を行っていきます。

今日の夕飯は「餃子」でした。それではまた。。

コメント

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